001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2008 Sun Microsystems, Inc. 015 * Portions Copyright 2014-2016 ForgeRock AS. 016 */ 017package org.opends.server.authorization.dseecompat; 018 019import static org.opends.messages.AccessControlMessages.*; 020import static org.opends.messages.SchemaMessages.*; 021import static org.opends.server.util.CollectionUtils.*; 022import static org.opends.server.util.StaticUtils.*; 023 024import java.util.ArrayList; 025import java.util.List; 026 027import org.forgerock.i18n.LocalizableMessage; 028import org.forgerock.i18n.slf4j.LocalizedLogger; 029import org.forgerock.opendj.ldap.ByteString; 030import org.forgerock.opendj.ldap.ResultCode; 031import org.forgerock.util.Reject; 032import org.forgerock.opendj.ldap.DN; 033import org.opends.server.types.DirectoryException; 034 035/** 036 * This class is used to encapsulate DN pattern matching using wildcards. 037 * The following wildcard uses are supported. 038 * 039 * Value substring: Any number of wildcards may appear in RDN attribute 040 * values where they match zero or more characters, just like substring filters: 041 * uid=b*jensen* 042 * 043 * Whole-Type: A single wildcard may also be used to match any RDN attribute 044 * type, and the wildcard in this case may be omitted as a shorthand: 045 * *=bjensen 046 * bjensen 047 * 048 * Whole-RDN. A single wildcard may be used to match exactly one RDN component 049 * (which may be single or multi-valued): 050 * *,dc=example,dc=com 051 * 052 * Multiple-Whole-RDN: A double wildcard may be used to match one or more 053 * RDN components: 054 * uid=bjensen,**,dc=example,dc=com 055 * 056 */ 057public class PatternDN 058{ 059 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 060 061 /** 062 * If the pattern did not include any Multiple-Whole-RDN wildcards, then 063 * this is the sequence of RDN patterns in the DN pattern. Otherwise it 064 * is null. 065 */ 066 PatternRDN[] equality; 067 068 069 /** 070 * If the pattern included any Multiple-Whole-RDN wildcards, then these 071 * are the RDN pattern sequences that appear between those wildcards. 072 */ 073 PatternRDN[] subInitial; 074 List<PatternRDN[]> subAnyElements; 075 PatternRDN[] subFinal; 076 077 078 /** 079 * When there is no initial sequence, this is used to distinguish between 080 * the case where we have a suffix pattern (zero or more RDN components 081 * allowed before matching elements) and the case where it is not a 082 * suffix pattern but the pattern started with a Multiple-Whole-RDN wildcard 083 * (one or more RDN components allowed before matching elements). 084 */ 085 boolean isSuffix; 086 087 088 /** 089 * Create a DN pattern that does not include any Multiple-Whole-RDN wildcards. 090 * @param equality The sequence of RDN patterns making up the DN pattern. 091 */ 092 private PatternDN(PatternRDN[] equality) 093 { 094 this.equality = equality; 095 } 096 097 098 /** 099 * Create a DN pattern that includes Multiple-Whole-RDN wildcards. 100 * @param subInitial The sequence of RDN patterns appearing at the 101 * start of the DN, or null if there are none. 102 * @param subAnyElements The list of sequences of RDN patterns appearing 103 * in order anywhere in the DN. 104 * @param subFinal The sequence of RDN patterns appearing at the 105 * end of the DN, or null if there are none. 106 */ 107 private PatternDN(PatternRDN[] subInitial, 108 List<PatternRDN[]> subAnyElements, 109 PatternRDN[] subFinal) 110 { 111 Reject.ifNull(subAnyElements); 112 this.subInitial = subInitial; 113 this.subAnyElements = subAnyElements; 114 this.subFinal = subFinal; 115 } 116 117 118 /** 119 * Determine whether a given DN matches this pattern. 120 * @param dn The DN to be matched. 121 * @return true if the DN matches the pattern. 122 */ 123 public boolean matchesDN(DN dn) 124 { 125 if (equality != null) 126 { 127 // There are no Multiple-Whole-RDN wildcards in the pattern. 128 if (equality.length != dn.size()) 129 { 130 return false; 131 } 132 133 for (int i = 0; i < dn.size(); i++) 134 { 135 if (!equality[i].matchesRDN(dn.rdn(i))) 136 { 137 return false; 138 } 139 } 140 141 return true; 142 } 143 else 144 { 145 // There are Multiple-Whole-RDN wildcards in the pattern. 146 int valueLength = dn.size(); 147 148 int pos = 0; 149 if (subInitial != null) 150 { 151 int initialLength = subInitial.length; 152 if (initialLength > valueLength) 153 { 154 return false; 155 } 156 157 for (; pos < initialLength; pos++) 158 { 159 if (!subInitial[pos].matchesRDN(dn.rdn(pos))) 160 { 161 return false; 162 } 163 } 164 pos++; 165 } 166 else 167 { 168 if (!isSuffix) 169 { 170 pos++; 171 } 172 } 173 174 175 if (subAnyElements != null && ! subAnyElements.isEmpty()) 176 { 177 for (PatternRDN[] element : subAnyElements) 178 { 179 int anyLength = element.length; 180 181 int end = valueLength - anyLength; 182 boolean match = false; 183 for (; pos < end; pos++) 184 { 185 if (element[0].matchesRDN(dn.rdn(pos))) 186 { 187 if (subMatch(dn, pos, element, anyLength)) 188 { 189 match = true; 190 break; 191 } 192 } 193 } 194 195 if (match) 196 { 197 pos += anyLength + 1; 198 } 199 else 200 { 201 return false; 202 } 203 } 204 } 205 206 207 if (subFinal != null) 208 { 209 int finalLength = subFinal.length; 210 211 if (valueLength - finalLength < pos) 212 { 213 return false; 214 } 215 216 pos = valueLength - finalLength; 217 for (int i=0; i < finalLength; i++,pos++) 218 { 219 if (!subFinal[i].matchesRDN(dn.rdn(pos))) 220 { 221 return false; 222 } 223 } 224 } 225 226 return pos <= valueLength; 227 } 228 } 229 230 private boolean subMatch(DN dn, int pos, PatternRDN[] element, int length) 231 { 232 for (int i = 1; i < length; i++) 233 { 234 if (!element[i].matchesRDN(dn.rdn(pos + i))) 235 { 236 return false; 237 } 238 } 239 return true; 240 } 241 242 /** 243 * Create a new DN pattern matcher to match a suffix. 244 * @param pattern The suffix pattern string. 245 * @throws org.opends.server.types.DirectoryException If the pattern string 246 * is not valid. 247 * @return A new DN pattern matcher. 248 */ 249 public static PatternDN decodeSuffix(String pattern) 250 throws DirectoryException 251 { 252 // Parse the user supplied pattern. 253 PatternDN patternDN = decode(pattern); 254 255 // Adjust the pattern so that it matches any DN ending with the pattern. 256 if (patternDN.equality != null) 257 { 258 // The pattern contained no Multiple-Whole-RDN wildcards, 259 // so we just convert the whole thing into a final fragment. 260 patternDN.subInitial = null; 261 patternDN.subFinal = patternDN.equality; 262 patternDN.subAnyElements = null; 263 patternDN.equality = null; 264 } 265 else if (patternDN.subInitial != null) 266 { 267 // The pattern had an initial fragment so we need to convert that into 268 // the head of the list of any elements. 269 patternDN.subAnyElements.add(0, patternDN.subInitial); 270 patternDN.subInitial = null; 271 } 272 patternDN.isSuffix = true; 273 return patternDN; 274 } 275 276 277 /** 278 * Create a new DN pattern matcher from a pattern string. 279 * @param dnString The DN pattern string. 280 * @throws org.opends.server.types.DirectoryException If the pattern string 281 * is not valid. 282 * @return A new DN pattern matcher. 283 */ 284 public static PatternDN decode(String dnString) 285 throws DirectoryException 286 { 287 ArrayList<PatternRDN> rdnComponents = new ArrayList<>(); 288 ArrayList<Integer> doubleWildPos = new ArrayList<>(); 289 290 // A null or empty DN is acceptable. 291 if (dnString == null) 292 { 293 return new PatternDN(new PatternRDN[0]); 294 } 295 296 int length = dnString.length(); 297 if (length == 0) 298 { 299 return new PatternDN(new PatternRDN[0]); 300 } 301 302 303 // Iterate through the DN string. The first thing to do is to get 304 // rid of any leading spaces. 305 int pos = 0; 306 char c = dnString.charAt(pos); 307 while (c == ' ') 308 { 309 pos++; 310 if (pos == length) 311 { 312 // This means that the DN was completely comprised of spaces 313 // and therefore should be considered the same as a null or empty DN. 314 return new PatternDN(new PatternRDN[0]); 315 } 316 else 317 { 318 c = dnString.charAt(pos); 319 } 320 } 321 322 // We know that it's not an empty DN, so we can do the real 323 // processing. Create a loop and iterate through all the RDN components. 324 rdnLoop: 325 while (true) 326 { 327 int attributePos = pos; 328 StringBuilder attributeName = new StringBuilder(); 329 pos = parseAttributePattern(dnString, pos, attributeName); 330 String name = attributeName.toString(); 331 332 333 // Make sure that we're not at the end of the DN string because 334 // that would be invalid. 335 if (pos >= length) 336 { 337 if (name.equals("*")) 338 { 339 rdnComponents.add(new PatternRDN(name, null, dnString)); 340 break; 341 } 342 else if (name.equals("**")) 343 { 344 doubleWildPos.add(rdnComponents.size()); 345 break; 346 } 347 else 348 { 349 pos = attributePos - 1; 350 name = "*"; 351 c = '='; 352 } 353 } 354 else 355 { 356 // Skip over any spaces between the attribute name and its 357 // value. 358 c = dnString.charAt(pos); 359 while (c == ' ') 360 { 361 pos++; 362 if (pos >= length) 363 { 364 if (name.equals("*")) 365 { 366 rdnComponents.add(new PatternRDN(name, null, dnString)); 367 break rdnLoop; 368 } 369 else if (name.equals("**")) 370 { 371 doubleWildPos.add(rdnComponents.size()); 372 break rdnLoop; 373 } 374 else 375 { 376 pos = attributePos - 1; 377 name = "*"; 378 c = '='; 379 } 380 } 381 else 382 { 383 c = dnString.charAt(pos); 384 } 385 } 386 } 387 388 389 if (c == '=') 390 { 391 pos++; 392 } 393 else if (c == ',' || c == ';') 394 { 395 if (name.equals("*")) 396 { 397 rdnComponents.add(new PatternRDN(name, null, dnString)); 398 pos++; 399 continue; 400 } 401 else if (name.equals("**")) 402 { 403 doubleWildPos.add(rdnComponents.size()); 404 pos++; 405 continue; 406 } 407 else 408 { 409 pos = attributePos; 410 name = "*"; 411 } 412 } 413 else 414 { 415 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 416 ERR_ATTR_SYNTAX_DN_NO_EQUAL.get(dnString, attributeName, c)); 417 } 418 419 // Skip over any spaces after the equal sign. 420 while (pos < length && dnString.charAt(pos) == ' ') 421 { 422 pos++; 423 } 424 425 426 // If we are at the end of the DN string, then that must mean 427 // that the attribute value was empty. This will probably never 428 // happen in a real-world environment, but technically isn't 429 // illegal. If it does happen, then go ahead and create the 430 // RDN component and return the DN. 431 if (pos >= length) 432 { 433 ArrayList<ByteString> arrayList = newArrayList(ByteString.empty()); 434 rdnComponents.add(new PatternRDN(name, arrayList, dnString)); 435 break; 436 } 437 438 439 // Parse the value for this RDN component. 440 ArrayList<ByteString> parsedValue = new ArrayList<>(); 441 pos = parseValuePattern(dnString, pos, parsedValue); 442 443 444 // Create the new RDN with the provided information. 445 PatternRDN rdn = new PatternRDN(name, parsedValue, dnString); 446 447 448 // Skip over any spaces that might be after the attribute value. 449 while (pos < length && ((c = dnString.charAt(pos)) == ' ')) 450 { 451 pos++; 452 } 453 454 455 // Most likely, we will be at either the end of the RDN 456 // component or the end of the DN. If so, then handle that appropriately. 457 if (pos >= length) 458 { 459 // We're at the end of the DN string and should have a valid DN so return it. 460 rdnComponents.add(rdn); 461 break; 462 } 463 else if (c == ',' || c == ';') 464 { 465 // We're at the end of the RDN component, so add it to the list, 466 // skip over the comma/semicolon, and start on the next component. 467 rdnComponents.add(rdn); 468 pos++; 469 continue; 470 } 471 else if (c != '+') 472 { 473 // This should not happen. At any rate, it's an illegal 474 // character, so throw an exception. 475 LocalizableMessage message = ERR_ATTR_SYNTAX_DN_INVALID_CHAR.get(dnString, c, pos); 476 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message); 477 } 478 479 480 // If we have gotten here, then this must be a multi-valued RDN. 481 // In that case, parse the remaining attribute/value pairs and 482 // add them to the RDN that we've already created. 483 while (true) 484 { 485 // Skip over the plus sign and any spaces that may follow it 486 // before the next attribute name. 487 pos++; 488 while (pos < length && dnString.charAt(pos) == ' ') 489 { 490 pos++; 491 } 492 493 494 // Parse the attribute name from the DN string. 495 attributeName = new StringBuilder(); 496 pos = parseAttributePattern(dnString, pos, attributeName); 497 498 499 // Make sure that we're not at the end of the DN string 500 // because that would be invalid. 501 if (pos >= length) 502 { 503 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 504 ERR_ATTR_SYNTAX_DN_END_WITH_ATTR_NAME.get(dnString, attributeName)); 505 } 506 507 508 name = attributeName.toString(); 509 510 // Skip over any spaces between the attribute name and its 511 // value. 512 c = dnString.charAt(pos); 513 while (c == ' ') 514 { 515 pos++; 516 if (pos >= length) 517 { 518 // This means that we hit the end of the value before 519 // finding a '='. This is illegal because there is no 520 // attribute-value separator. 521 LocalizableMessage message = 522 ERR_ATTR_SYNTAX_DN_END_WITH_ATTR_NAME.get(dnString, name); 523 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 524 message); 525 } 526 else 527 { 528 c = dnString.charAt(pos); 529 } 530 } 531 532 533 // The next character must be an equal sign. If it is not, 534 // then that's an error. 535 if (c == '=') 536 { 537 pos++; 538 } 539 else 540 { 541 LocalizableMessage message = ERR_ATTR_SYNTAX_DN_NO_EQUAL.get(dnString, name, c); 542 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 543 message); 544 } 545 546 547 // Skip over any spaces after the equal sign. 548 while (pos < length && ((c = dnString.charAt(pos)) == ' ')) 549 { 550 pos++; 551 } 552 553 554 // If we are at the end of the DN string, then that must mean 555 // that the attribute value was empty. This will probably 556 // never happen in a real-world environment, but technically 557 // isn't illegal. If it does happen, then go ahead and create 558 // the RDN component and return the DN. 559 if (pos >= length) 560 { 561 ArrayList<ByteString> arrayList = newArrayList(ByteString.empty()); 562 rdn.addValue(name, arrayList, dnString); 563 rdnComponents.add(rdn); 564 break; 565 } 566 567 568 // Parse the value for this RDN component. 569 parsedValue = new ArrayList<>(); 570 pos = parseValuePattern(dnString, pos, parsedValue); 571 572 573 // Create the new RDN with the provided information. 574 rdn.addValue(name, parsedValue, dnString); 575 576 577 // Skip over any spaces that might be after the attribute value. 578 while (pos < length && ((c = dnString.charAt(pos)) == ' ')) 579 { 580 pos++; 581 } 582 583 584 // Most likely, we will be at either the end of the RDN 585 // component or the end of the DN. If so, then handle that appropriately. 586 if (pos >= length) 587 { 588 // We're at the end of the DN string and should have a valid 589 // DN so return it. 590 rdnComponents.add(rdn); 591 break; 592 } 593 else if (c == ',' || c == ';') 594 { 595 // We're at the end of the RDN component, so add it to the 596 // list, skip over the comma/semicolon, and start on the next component. 597 rdnComponents.add(rdn); 598 pos++; 599 break; 600 } 601 else if (c != '+') 602 { 603 // This should not happen. At any rate, it's an illegal 604 // character, so throw an exception. 605 LocalizableMessage message = 606 ERR_ATTR_SYNTAX_DN_INVALID_CHAR.get(dnString, c, pos); 607 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message); 608 } 609 } 610 } 611 612 if (doubleWildPos.isEmpty()) 613 { 614 PatternRDN[] patterns = new PatternRDN[rdnComponents.size()]; 615 patterns = rdnComponents.toArray(patterns); 616 return new PatternDN(patterns); 617 } 618 else 619 { 620 PatternRDN[] subInitial = null; 621 PatternRDN[] subFinal = null; 622 List<PatternRDN[]> subAnyElements = new ArrayList<>(); 623 624 int i = 0; 625 int numComponents = rdnComponents.size(); 626 627 int to = doubleWildPos.get(i); 628 if (to != 0) 629 { 630 // Initial piece. 631 subInitial = new PatternRDN[to]; 632 subInitial = rdnComponents.subList(0, to).toArray(subInitial); 633 } 634 635 int from; 636 for (; i < doubleWildPos.size() - 1; i++) 637 { 638 from = doubleWildPos.get(i); 639 to = doubleWildPos.get(i+1); 640 PatternRDN[] subAny = new PatternRDN[to-from]; 641 subAny = rdnComponents.subList(from, to).toArray(subAny); 642 subAnyElements.add(subAny); 643 } 644 645 if (i < doubleWildPos.size()) 646 { 647 from = doubleWildPos.get(i); 648 if (from != numComponents) 649 { 650 // Final piece. 651 subFinal = new PatternRDN[numComponents-from]; 652 subFinal = rdnComponents.subList(from, numComponents). 653 toArray(subFinal); 654 } 655 } 656 657 return new PatternDN(subInitial, subAnyElements, subFinal); 658 } 659 } 660 661 662 /** 663 * Parses an attribute name pattern from the provided DN pattern string 664 * starting at the specified location. 665 * 666 * @param dnString The DN pattern string to be parsed. 667 * @param pos The position at which to start parsing 668 * the attribute name pattern. 669 * @param attributeName The buffer to which to append the parsed 670 * attribute name pattern. 671 * 672 * @return The position of the first character that is not part of 673 * the attribute name pattern. 674 * 675 * @throws DirectoryException If it was not possible to parse a 676 * valid attribute name pattern from the 677 * provided DN pattern string. 678 */ 679 static int parseAttributePattern(String dnString, int pos, 680 StringBuilder attributeName) 681 throws DirectoryException 682 { 683 int length = dnString.length(); 684 685 686 // Skip over any leading spaces. 687 if (pos < length) 688 { 689 while (dnString.charAt(pos) == ' ') 690 { 691 pos++; 692 if (pos == length) 693 { 694 // This means that the remainder of the DN was completely 695 // comprised of spaces. If we have gotten here, then we 696 // know that there is at least one RDN component, and 697 // therefore the last non-space character of the DN must 698 // have been a comma. This is not acceptable. 699 LocalizableMessage message = ERR_ATTR_SYNTAX_DN_END_WITH_COMMA.get(dnString); 700 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 701 message); 702 } 703 } 704 } 705 706 // Next, we should find the attribute name for this RDN component. 707 boolean checkForOID = false; 708 boolean endOfName = false; 709 while (pos < length) 710 { 711 // To make the switch more efficient, we'll include all ASCII 712 // characters in the range of allowed values and then reject the 713 // ones that aren't allowed. 714 char c = dnString.charAt(pos); 715 switch (c) 716 { 717 case ' ': 718 // This should denote the end of the attribute name. 719 endOfName = true; 720 break; 721 722 723 case '!': 724 case '"': 725 case '#': 726 case '$': 727 case '%': 728 case '&': 729 case '\'': 730 case '(': 731 case ')': 732 // None of these are allowed in an attribute name or any 733 // character immediately following it. 734 LocalizableMessage message = 735 ERR_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR.get(dnString, c, pos); 736 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 737 message); 738 739 740 case '*': 741 // Wildcard character. 742 attributeName.append(c); 743 break; 744 745 case '+': 746 // None of these are allowed in an attribute name or any 747 // character immediately following it. 748 message = 749 ERR_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR.get(dnString, c, pos); 750 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 751 message); 752 753 754 case ',': 755 // This should denote the end of the attribute name. 756 endOfName = true; 757 break; 758 759 case '-': 760 // This will be allowed as long as it isn't the first 761 // character in the attribute name. 762 if (attributeName.length() > 0) 763 { 764 attributeName.append(c); 765 } 766 else 767 { 768 message = 769 ERR_ATTR_SYNTAX_DN_ATTR_ILLEGAL_INITIAL_DASH.get(dnString); 770 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 771 message); 772 } 773 break; 774 775 776 case '.': 777 // The period could be allowed if the attribute name is 778 // actually expressed as an OID. We'll accept it for now, 779 // but make sure to check it later. 780 attributeName.append(c); 781 checkForOID = true; 782 break; 783 784 785 case '/': 786 // This is not allowed in an attribute name or any character 787 // immediately following it. 788 message = 789 ERR_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR.get(dnString, c, pos); 790 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 791 message); 792 793 794 case '0': 795 case '1': 796 case '2': 797 case '3': 798 case '4': 799 case '5': 800 case '6': 801 case '7': 802 case '8': 803 case '9': 804 // Digits are always allowed if they are not the first 805 // character. However, they may be allowed if they are the 806 // first character if the valid is an OID or if the 807 // attribute name exceptions option is enabled. Therefore, 808 // we'll accept it now and check it later. 809 attributeName.append(c); 810 break; 811 812 813 case ':': 814 // Not allowed in an attribute name or any 815 // character immediately following it. 816 message = 817 ERR_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR.get(dnString, c, pos); 818 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 819 message); 820 821 822 case ';': // NOTE: attribute options are not allowed in a DN. 823 // This should denote the end of the attribute name. 824 endOfName = true; 825 break; 826 827 case '<': 828 // None of these are allowed in an attribute name or any 829 // character immediately following it. 830 message = 831 ERR_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR.get(dnString, c, pos); 832 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 833 message); 834 835 836 case '=': 837 // This should denote the end of the attribute name. 838 endOfName = true; 839 break; 840 841 842 case '>': 843 case '?': 844 case '@': 845 // None of these are allowed in an attribute name or any 846 // character immediately following it. 847 message = 848 ERR_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR.get(dnString, c, pos); 849 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 850 message); 851 852 853 case 'A': 854 case 'B': 855 case 'C': 856 case 'D': 857 case 'E': 858 case 'F': 859 case 'G': 860 case 'H': 861 case 'I': 862 case 'J': 863 case 'K': 864 case 'L': 865 case 'M': 866 case 'N': 867 case 'O': 868 case 'P': 869 case 'Q': 870 case 'R': 871 case 'S': 872 case 'T': 873 case 'U': 874 case 'V': 875 case 'W': 876 case 'X': 877 case 'Y': 878 case 'Z': 879 // These will always be allowed. 880 attributeName.append(c); 881 break; 882 883 884 case '[': 885 case '\\': 886 case ']': 887 case '^': 888 // None of these are allowed in an attribute name or any 889 // character immediately following it. 890 message = 891 ERR_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR.get(dnString, c, pos); 892 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 893 message); 894 895 896 case '_': 897 attributeName.append(c); 898 break; 899 900 901 case '`': 902 // This is not allowed in an attribute name or any character 903 // immediately following it. 904 message = 905 ERR_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR.get(dnString, c, pos); 906 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 907 message); 908 909 910 case 'a': 911 case 'b': 912 case 'c': 913 case 'd': 914 case 'e': 915 case 'f': 916 case 'g': 917 case 'h': 918 case 'i': 919 case 'j': 920 case 'k': 921 case 'l': 922 case 'm': 923 case 'n': 924 case 'o': 925 case 'p': 926 case 'q': 927 case 'r': 928 case 's': 929 case 't': 930 case 'u': 931 case 'v': 932 case 'w': 933 case 'x': 934 case 'y': 935 case 'z': 936 // These will always be allowed. 937 attributeName.append(c); 938 break; 939 940 941 default: 942 // This is not allowed in an attribute name or any character 943 // immediately following it. 944 message = 945 ERR_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR.get(dnString, c, pos); 946 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 947 message); 948 } 949 950 951 if (endOfName) 952 { 953 break; 954 } 955 956 pos++; 957 } 958 959 960 // We should now have the full attribute name. However, we may 961 // still need to perform some validation, particularly if the 962 // name contains a period or starts with a digit. It must also 963 // have at least one character. 964 if (attributeName.length() == 0) 965 { 966 LocalizableMessage message = ERR_ATTR_SYNTAX_DN_ATTR_NO_NAME.get(dnString); 967 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 968 message); 969 } 970 else if (checkForOID) 971 { 972 boolean validOID = true; 973 974 int namePos = 0; 975 int nameLength = attributeName.length(); 976 char ch0 = attributeName.charAt(0); 977 if (ch0 == 'o' || ch0 == 'O') 978 { 979 if (nameLength <= 4) 980 { 981 validOID = false; 982 } 983 else 984 { 985 char ch1 = attributeName.charAt(1); 986 char ch2 = attributeName.charAt(2); 987 if ((ch1 == 'i' || ch1 == 'I') 988 && (ch2 == 'd' || ch2 == 'D') 989 && attributeName.charAt(3) == '.') 990 { 991 attributeName.delete(0, 4); 992 nameLength -= 4; 993 } 994 else 995 { 996 validOID = false; 997 } 998 } 999 } 1000 1001 while (validOID && namePos < nameLength) 1002 { 1003 char ch = attributeName.charAt(namePos++); 1004 if (isDigit(ch)) 1005 { 1006 while (validOID && namePos < nameLength && 1007 isDigit(attributeName.charAt(namePos))) 1008 { 1009 namePos++; 1010 } 1011 1012 if (namePos < nameLength && 1013 attributeName.charAt(namePos) != '.') 1014 { 1015 validOID = false; 1016 } 1017 } 1018 else if (ch == '.') 1019 { 1020 if (namePos == 1 || 1021 attributeName.charAt(namePos-2) == '.') 1022 { 1023 validOID = false; 1024 } 1025 } 1026 else 1027 { 1028 validOID = false; 1029 } 1030 } 1031 1032 1033 if (validOID && attributeName.charAt(nameLength-1) == '.') 1034 { 1035 validOID = false; 1036 } 1037 1038 if (! validOID) 1039 { 1040 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 1041 ERR_ATTR_SYNTAX_DN_ATTR_ILLEGAL_PERIOD.get(dnString, attributeName)); 1042 } 1043 } 1044 1045 return pos; 1046 } 1047 1048 1049 /** 1050 * Parses the attribute value pattern from the provided DN pattern 1051 * string starting at the specified location. The value is split up 1052 * according to the wildcard locations, and the fragments are inserted 1053 * into the provided list. 1054 * 1055 * @param dnString The DN pattern string to be parsed. 1056 * @param pos The position of the first character in 1057 * the attribute value pattern to parse. 1058 * @param attributeValues The list whose elements should be set to 1059 * the parsed attribute value fragments when 1060 * this method completes successfully. 1061 * 1062 * @return The position of the first character that is not part of 1063 * the attribute value. 1064 * 1065 * @throws DirectoryException If it was not possible to parse a 1066 * valid attribute value pattern from the 1067 * provided DN string. 1068 */ 1069 private static int parseValuePattern(String dnString, int pos, 1070 ArrayList<ByteString> attributeValues) 1071 throws DirectoryException 1072 { 1073 // All leading spaces have already been stripped so we can start 1074 // reading the value. However, it may be empty so check for that. 1075 int length = dnString.length(); 1076 if (pos >= length) 1077 { 1078 return pos; 1079 } 1080 1081 1082 // Look at the first character. If it is an octothorpe (#), then 1083 // that means that the value should be a hex string. 1084 char c = dnString.charAt(pos++); 1085 if (c == '#') 1086 { 1087 // The first two characters must be hex characters. 1088 StringBuilder hexString = new StringBuilder(); 1089 if (pos+2 > length) 1090 { 1091 LocalizableMessage message = ERR_ATTR_SYNTAX_DN_HEX_VALUE_TOO_SHORT.get(dnString); 1092 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 1093 message); 1094 } 1095 1096 for (int i=0; i < 2; i++) 1097 { 1098 c = dnString.charAt(pos++); 1099 if (isHexDigit(c)) 1100 { 1101 hexString.append(c); 1102 } 1103 else 1104 { 1105 LocalizableMessage message = 1106 ERR_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT.get(dnString, c); 1107 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 1108 message); 1109 } 1110 } 1111 1112 1113 // The rest of the value must be a multiple of two hex 1114 // characters. The end of the value may be designated by the 1115 // end of the DN, a comma or semicolon, or a space. 1116 while (pos < length) 1117 { 1118 c = dnString.charAt(pos++); 1119 if (isHexDigit(c)) 1120 { 1121 hexString.append(c); 1122 1123 if (pos < length) 1124 { 1125 c = dnString.charAt(pos++); 1126 if (isHexDigit(c)) 1127 { 1128 hexString.append(c); 1129 } 1130 else 1131 { 1132 LocalizableMessage message = 1133 ERR_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT.get(dnString, c); 1134 throw new DirectoryException( 1135 ResultCode.INVALID_DN_SYNTAX, message); 1136 } 1137 } 1138 else 1139 { 1140 LocalizableMessage message = 1141 ERR_ATTR_SYNTAX_DN_HEX_VALUE_TOO_SHORT.get(dnString); 1142 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 1143 message); 1144 } 1145 } 1146 else if (c == ' ' || c == ',' || c == ';') 1147 { 1148 // This denotes the end of the value. 1149 pos--; 1150 break; 1151 } 1152 else 1153 { 1154 LocalizableMessage message = 1155 ERR_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT.get(dnString, c); 1156 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 1157 message); 1158 } 1159 } 1160 1161 1162 // At this point, we should have a valid hex string. Convert it 1163 // to a byte array and set that as the value of the provided 1164 // octet string. 1165 try 1166 { 1167 byte[] bytes = hexStringToByteArray(hexString.toString()); 1168 attributeValues.add(ByteString.wrap(bytes)); 1169 return pos; 1170 } 1171 catch (Exception e) 1172 { 1173 logger.traceException(e); 1174 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 1175 ERR_ATTR_SYNTAX_DN_ATTR_VALUE_DECODE_FAILURE.get(dnString, e)); 1176 } 1177 } 1178 1179 1180 // If the first character is a quotation mark, then the value 1181 // should continue until the corresponding closing quotation mark. 1182 else if (c == '"') 1183 { 1184 // Keep reading until we find an unescaped closing quotation 1185 // mark. 1186 boolean escaped = false; 1187 StringBuilder valueString = new StringBuilder(); 1188 while (true) 1189 { 1190 if (pos >= length) 1191 { 1192 // We hit the end of the DN before the closing quote. 1193 // That's an error. 1194 LocalizableMessage message = ERR_ATTR_SYNTAX_DN_UNMATCHED_QUOTE.get(dnString); 1195 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 1196 message); 1197 } 1198 1199 c = dnString.charAt(pos++); 1200 if (escaped) 1201 { 1202 // The previous character was an escape, so we'll take this 1203 // one no matter what. 1204 valueString.append(c); 1205 escaped = false; 1206 } 1207 else if (c == '\\') 1208 { 1209 // The next character is escaped. Set a flag to denote 1210 // this, but don't include the backslash. 1211 escaped = true; 1212 } 1213 else if (c == '"') 1214 { 1215 // This is the end of the value. 1216 break; 1217 } 1218 else 1219 { 1220 // This is just a regular character that should be in the 1221 // value. 1222 valueString.append(c); 1223 } 1224 } 1225 1226 attributeValues.add(ByteString.valueOfUtf8(valueString)); 1227 return pos; 1228 } 1229 1230 1231 // Otherwise, use general parsing to find the end of the value. 1232 else 1233 { 1234 boolean escaped; 1235 StringBuilder valueString = new StringBuilder(); 1236 StringBuilder hexChars = new StringBuilder(); 1237 1238 if (c == '\\') 1239 { 1240 escaped = true; 1241 } 1242 else if (c == '*') 1243 { 1244 escaped = false; 1245 attributeValues.add(ByteString.valueOfUtf8(valueString)); 1246 } 1247 else 1248 { 1249 escaped = false; 1250 valueString.append(c); 1251 } 1252 1253 1254 // Keep reading until we find an unescaped comma or plus sign or 1255 // the end of the DN. 1256 while (true) 1257 { 1258 if (pos >= length) 1259 { 1260 // This is the end of the DN and therefore the end of the 1261 // value. If there are any hex characters, then we need to 1262 // deal with them accordingly. 1263 appendHexChars(dnString, valueString, hexChars); 1264 break; 1265 } 1266 1267 c = dnString.charAt(pos++); 1268 if (escaped) 1269 { 1270 // The previous character was an escape, so we'll take this 1271 // one. However, this could be a hex digit, and if that's 1272 // the case then the escape would actually be in front of 1273 // two hex digits that should be treated as a special 1274 // character. 1275 if (isHexDigit(c)) 1276 { 1277 // It is a hexadecimal digit, so the next digit must be 1278 // one too. However, this could be just one in a series 1279 // of escaped hex pairs that is used in a string 1280 // containing one or more multi-byte UTF-8 characters so 1281 // we can't just treat this byte in isolation. Collect 1282 // all the bytes together and make sure to take care of 1283 // these hex bytes before appending anything else to the 1284 // value. 1285 if (pos >= length) 1286 { 1287 LocalizableMessage message = 1288 ERR_ATTR_SYNTAX_DN_ESCAPED_HEX_VALUE_INVALID.get(dnString); 1289 throw new DirectoryException( 1290 ResultCode.INVALID_DN_SYNTAX, message); 1291 } 1292 else 1293 { 1294 char c2 = dnString.charAt(pos++); 1295 if (isHexDigit(c2)) 1296 { 1297 hexChars.append(c); 1298 hexChars.append(c2); 1299 } 1300 else 1301 { 1302 LocalizableMessage message = 1303 ERR_ATTR_SYNTAX_DN_ESCAPED_HEX_VALUE_INVALID.get(dnString); 1304 throw new DirectoryException( 1305 ResultCode.INVALID_DN_SYNTAX, message); 1306 } 1307 } 1308 } 1309 else 1310 { 1311 appendHexChars(dnString, valueString, hexChars); 1312 valueString.append(c); 1313 } 1314 1315 escaped = false; 1316 } 1317 else if (c == '\\') 1318 { 1319 escaped = true; 1320 } 1321 else if (c == ',' || c == ';') 1322 { 1323 appendHexChars(dnString, valueString, hexChars); 1324 pos--; 1325 break; 1326 } 1327 else if (c == '+') 1328 { 1329 appendHexChars(dnString, valueString, hexChars); 1330 pos--; 1331 break; 1332 } 1333 else if (c == '*') 1334 { 1335 appendHexChars(dnString, valueString, hexChars); 1336 if (valueString.length() == 0) 1337 { 1338 LocalizableMessage message = 1339 WARN_PATTERN_DN_CONSECUTIVE_WILDCARDS_IN_VALUE.get(dnString); 1340 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 1341 message); 1342 } 1343 attributeValues.add(ByteString.valueOfUtf8(valueString)); 1344 valueString = new StringBuilder(); 1345 hexChars = new StringBuilder(); 1346 } 1347 else 1348 { 1349 appendHexChars(dnString, valueString, hexChars); 1350 valueString.append(c); 1351 } 1352 } 1353 1354 1355 // Strip off any unescaped spaces that may be at the end of the 1356 // value. 1357 if (pos > 2 && dnString.charAt(pos-1) == ' ' && 1358 dnString.charAt(pos-2) != '\\') 1359 { 1360 int lastPos = valueString.length() - 1; 1361 while (lastPos > 0) 1362 { 1363 if (valueString.charAt(lastPos) == ' ') 1364 { 1365 valueString.delete(lastPos, lastPos+1); 1366 lastPos--; 1367 } 1368 else 1369 { 1370 break; 1371 } 1372 } 1373 } 1374 1375 1376 attributeValues.add(ByteString.valueOfUtf8(valueString)); 1377 return pos; 1378 } 1379 } 1380 1381 1382 /** 1383 * Decodes a hexadecimal string from the provided 1384 * <CODE>hexChars</CODE> buffer, converts it to a byte array, and 1385 * then converts that to a UTF-8 string. The resulting UTF-8 string 1386 * will be appended to the provided <CODE>valueString</CODE> buffer, 1387 * and the <CODE>hexChars</CODE> buffer will be cleared. 1388 * 1389 * @param dnString The DN string that is being decoded. 1390 * @param valueString The buffer containing the value to which the 1391 * decoded string should be appended. 1392 * @param hexChars The buffer containing the hexadecimal 1393 * characters to decode to a UTF-8 string. 1394 * 1395 * @throws DirectoryException If any problem occurs during the 1396 * decoding process. 1397 */ 1398 private static void appendHexChars(String dnString, 1399 StringBuilder valueString, 1400 StringBuilder hexChars) 1401 throws DirectoryException 1402 { 1403 try 1404 { 1405 byte[] hexBytes = hexStringToByteArray(hexChars.toString()); 1406 valueString.append(new String(hexBytes, "UTF-8")); 1407 hexChars.delete(0, hexChars.length()); 1408 } 1409 catch (Exception e) 1410 { 1411 logger.traceException(e); 1412 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, 1413 ERR_ATTR_SYNTAX_DN_ATTR_VALUE_DECODE_FAILURE.get(dnString, e)); 1414 } 1415 } 1416}