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 }