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