1 /*
   2  * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.jndi.ldap;
  27 
  28 import javax.naming.*;
  29 import javax.naming.directory.*;
  30 import java.util.Vector;
  31 
  32 /**
  33  * Netscape's 3.1 servers have some schema bugs:
  34  * - It puts quotes around OIDs (such as those for SUP, SYNTAX).
  35  * - When you try to write out the MUST/MAY list (such as "MUST cn"),
  36  *   it wants ("MUST (cn)") instead
  37  */
  38 
  39 final class LdapSchemaParser {
  40 
  41     // do debugging
  42     private static final boolean debug = false;
  43 
  44 
  45     // names of attribute IDs in the LDAP schema entry
  46     static final String OBJECTCLASSDESC_ATTR_ID = "objectClasses";
  47     static final String ATTRIBUTEDESC_ATTR_ID = "attributeTypes";
  48     static final String SYNTAXDESC_ATTR_ID = "ldapSyntaxes";
  49     static final String MATCHRULEDESC_ATTR_ID = "matchingRules";
  50 
  51     // information for creating internal nodes in JNDI schema tree
  52     static final String OBJECTCLASS_DEFINITION_NAME =
  53                         "ClassDefinition";
  54     private static final String[] CLASS_DEF_ATTRS = {
  55                          "objectclass", "ClassDefinition"};
  56             static final String ATTRIBUTE_DEFINITION_NAME =
  57                         "AttributeDefinition";
  58     private static final String[] ATTR_DEF_ATTRS = {
  59                         "objectclass", "AttributeDefinition" };
  60             static final String SYNTAX_DEFINITION_NAME =
  61                         "SyntaxDefinition";
  62     private static final String[] SYNTAX_DEF_ATTRS = {
  63                         "objectclass", "SyntaxDefinition" };
  64             static final String MATCHRULE_DEFINITION_NAME =
  65                         "MatchingRule";
  66     private static final String[] MATCHRULE_DEF_ATTRS = {
  67                         "objectclass", "MatchingRule" };
  68 
  69     // special tokens used in LDAP schema descriptions
  70     private static final char   SINGLE_QUOTE = '\'';
  71     private static final char   WHSP = ' ';
  72     private static final char   OID_LIST_BEGIN = '(';
  73     private static final char   OID_LIST_END = ')';
  74     private static final char   OID_SEPARATOR = '$';
  75 
  76     // common IDs
  77     private static final String  NUMERICOID_ID = "NUMERICOID";
  78     private static final String        NAME_ID = "NAME";
  79     private static final String        DESC_ID = "DESC";
  80     private static final String    OBSOLETE_ID = "OBSOLETE";
  81     private static final String         SUP_ID = "SUP";
  82     private static final String     PRIVATE_ID = "X-";
  83 
  84     // Object Class specific IDs
  85     private static final String    ABSTRACT_ID = "ABSTRACT";
  86     private static final String  STRUCTURAL_ID = "STRUCTURAL";
  87     private static final String   AUXILIARY_ID = "AUXILIARY";
  88     private static final String        MUST_ID = "MUST";
  89     private static final String         MAY_ID = "MAY";
  90 
  91     // Attribute Type specific IDs
  92     private static final String    EQUALITY_ID = "EQUALITY";
  93     private static final String    ORDERING_ID = "ORDERING";
  94     private static final String      SUBSTR_ID = "SUBSTR";
  95     private static final String      SYNTAX_ID = "SYNTAX";
  96     private static final String  SINGLE_VAL_ID = "SINGLE-VALUE";
  97     private static final String  COLLECTIVE_ID = "COLLECTIVE";
  98     private static final String NO_USER_MOD_ID = "NO-USER-MODIFICATION";
  99     private static final String       USAGE_ID = "USAGE";
 100 
 101     // The string value we give to boolean variables
 102     private static final String SCHEMA_TRUE_VALUE = "true";
 103 
 104     // To get around writing schemas that crash Netscape server
 105     private boolean netscapeBug;
 106 
 107     LdapSchemaParser(boolean netscapeBug) {
 108         this.netscapeBug = netscapeBug;
 109     }
 110 
 111     final static void LDAP2JNDISchema(Attributes schemaAttrs,
 112         LdapSchemaCtx schemaRoot) throws NamingException {
 113         Attribute               objectClassesAttr = null;
 114         Attribute               attributeDefAttr = null;
 115         Attribute               syntaxDefAttr = null;
 116         Attribute               matchRuleDefAttr = null;
 117 
 118         objectClassesAttr = schemaAttrs.get(OBJECTCLASSDESC_ATTR_ID);
 119         if(objectClassesAttr != null) {
 120             objectDescs2ClassDefs(objectClassesAttr,schemaRoot);
 121         }
 122 
 123         attributeDefAttr = schemaAttrs.get(ATTRIBUTEDESC_ATTR_ID);
 124         if(attributeDefAttr != null) {
 125             attrDescs2AttrDefs(attributeDefAttr, schemaRoot);
 126         }
 127 
 128         syntaxDefAttr = schemaAttrs.get(SYNTAXDESC_ATTR_ID);
 129         if(syntaxDefAttr != null) {
 130             syntaxDescs2SyntaxDefs(syntaxDefAttr, schemaRoot);
 131         }
 132 
 133         matchRuleDefAttr = schemaAttrs.get(MATCHRULEDESC_ATTR_ID);
 134         if(matchRuleDefAttr != null) {
 135             matchRuleDescs2MatchRuleDefs(matchRuleDefAttr, schemaRoot);
 136         }
 137     }
 138 
 139     final private static DirContext objectDescs2ClassDefs(Attribute objDescsAttr,
 140                                                    LdapSchemaCtx schemaRoot)
 141         throws NamingException {
 142 
 143         NamingEnumeration<?> objDescs;
 144         Attributes                objDef;
 145         LdapSchemaCtx             classDefTree;
 146 
 147         // create the class def subtree
 148         Attributes attrs = new BasicAttributes(LdapClient.caseIgnore);
 149         attrs.put(CLASS_DEF_ATTRS[0], CLASS_DEF_ATTRS[1]);
 150         classDefTree = schemaRoot.setup(LdapSchemaCtx.OBJECTCLASS_ROOT,
 151             OBJECTCLASS_DEFINITION_NAME, attrs);
 152 
 153         objDescs = objDescsAttr.getAll();
 154         String currentName;
 155         while(objDescs.hasMore()) {
 156             String objDesc = (String)objDescs.next();
 157             try {
 158                 Object[] def = desc2Def(objDesc);
 159                 currentName = (String) def[0];
 160                 objDef = (Attributes) def[1];
 161                 classDefTree.setup(LdapSchemaCtx.OBJECTCLASS,
 162                     currentName, objDef);
 163             } catch (NamingException ne) {
 164                 // error occurred while parsing, ignore current entry
 165             }
 166         }
 167 
 168         return classDefTree;
 169     }
 170 
 171     final private static DirContext attrDescs2AttrDefs(Attribute attributeDescAttr,
 172                                                 LdapSchemaCtx schemaRoot)
 173         throws NamingException {
 174 
 175         NamingEnumeration<?> attrDescs;
 176         Attributes           attrDef;
 177         LdapSchemaCtx        attrDefTree;
 178 
 179         // create the AttributeDef subtree
 180         Attributes attrs = new BasicAttributes(LdapClient.caseIgnore);
 181         attrs.put(ATTR_DEF_ATTRS[0], ATTR_DEF_ATTRS[1]);
 182         attrDefTree = schemaRoot.setup(LdapSchemaCtx.ATTRIBUTE_ROOT,
 183             ATTRIBUTE_DEFINITION_NAME, attrs);
 184 
 185         attrDescs = attributeDescAttr.getAll();
 186         String currentName;
 187         while(attrDescs.hasMore()) {
 188             String attrDesc = (String)attrDescs.next();
 189             try {
 190                 Object[] def = desc2Def(attrDesc);
 191                 currentName = (String) def[0];
 192                 attrDef = (Attributes) def[1];
 193                 attrDefTree.setup(LdapSchemaCtx.ATTRIBUTE,
 194                     currentName, attrDef);
 195             } catch (NamingException ne) {
 196                 // error occurred while parsing, ignore current entry
 197             }
 198         }
 199 
 200         return attrDefTree;
 201     }
 202 
 203     final private static DirContext syntaxDescs2SyntaxDefs(
 204                                                 Attribute syntaxDescAttr,
 205                                                 LdapSchemaCtx schemaRoot)
 206         throws NamingException {
 207 
 208         NamingEnumeration<?> syntaxDescs;
 209         Attributes           syntaxDef;
 210         LdapSchemaCtx        syntaxDefTree;
 211 
 212         // create the SyntaxDef subtree
 213         Attributes attrs = new BasicAttributes(LdapClient.caseIgnore);
 214         attrs.put(SYNTAX_DEF_ATTRS[0], SYNTAX_DEF_ATTRS[1]);
 215         syntaxDefTree = schemaRoot.setup(LdapSchemaCtx.SYNTAX_ROOT,
 216             SYNTAX_DEFINITION_NAME, attrs);
 217 
 218         syntaxDescs = syntaxDescAttr.getAll();
 219         String currentName;
 220         while(syntaxDescs.hasMore()) {
 221             String syntaxDesc = (String)syntaxDescs.next();
 222             try {
 223                 Object[] def = desc2Def(syntaxDesc);
 224                 currentName = (String) def[0];
 225                 syntaxDef = (Attributes) def[1];
 226                 syntaxDefTree.setup(LdapSchemaCtx.SYNTAX,
 227                     currentName, syntaxDef);
 228             } catch (NamingException ne) {
 229                 // error occurred while parsing, ignore current entry
 230             }
 231         }
 232 
 233         return syntaxDefTree;
 234     }
 235 
 236     final private static DirContext matchRuleDescs2MatchRuleDefs(
 237                                                 Attribute matchRuleDescAttr,
 238                                                 LdapSchemaCtx schemaRoot)
 239         throws NamingException {
 240 
 241         NamingEnumeration<?> matchRuleDescs;
 242         Attributes           matchRuleDef;
 243         LdapSchemaCtx        matchRuleDefTree;
 244 
 245         // create the MatchRuleDef subtree
 246         Attributes attrs = new BasicAttributes(LdapClient.caseIgnore);
 247         attrs.put(MATCHRULE_DEF_ATTRS[0], MATCHRULE_DEF_ATTRS[1]);
 248         matchRuleDefTree = schemaRoot.setup(LdapSchemaCtx.MATCHRULE_ROOT,
 249             MATCHRULE_DEFINITION_NAME, attrs);
 250 
 251         matchRuleDescs = matchRuleDescAttr.getAll();
 252         String currentName;
 253         while(matchRuleDescs.hasMore()) {
 254             String matchRuleDesc = (String)matchRuleDescs.next();
 255             try {
 256                 Object[] def = desc2Def(matchRuleDesc);
 257                 currentName = (String) def[0];
 258                 matchRuleDef = (Attributes) def[1];
 259                 matchRuleDefTree.setup(LdapSchemaCtx.MATCHRULE,
 260                     currentName, matchRuleDef);
 261             } catch (NamingException ne) {
 262                 // error occurred while parsing, ignore current entry
 263             }
 264         }
 265 
 266         return matchRuleDefTree;
 267     }
 268 
 269     final private static Object[] desc2Def(String desc)
 270         throws NamingException {
 271             //System.err.println(desc);
 272 
 273         Attributes      attrs = new BasicAttributes(LdapClient.caseIgnore);
 274         Attribute       attr = null;
 275         int[]           pos = new int[]{1}; // tolerate missing leading space
 276         boolean         moreTags = true;
 277 
 278         // Always begins with <whsp numericoid whsp>
 279         attr = readNumericOID(desc, pos);
 280         String currentName = (String) attr.get(0);  // name is OID by default
 281         attrs.put(attr);
 282 
 283         skipWhitespace(desc, pos);
 284 
 285         while (moreTags) {
 286             attr = readNextTag(desc, pos);
 287             attrs.put(attr);
 288 
 289             if (attr.getID().equals(NAME_ID)) {
 290                 currentName = (String) attr.get(0);  // use NAME attribute as name
 291             }
 292 
 293             skipWhitespace(desc, pos);
 294 
 295             if( pos[0] >= desc.length() -1 ) {
 296                 moreTags = false;
 297             }
 298         }
 299 
 300         return new Object[] {currentName, attrs};
 301     }
 302 
 303     // returns the index of the first whitespace char of a linear whitespace
 304     // sequence ending at the given position.
 305     final private static int findTrailingWhitespace(String string, int pos) {
 306         for(int i = pos; i > 0; i--) {
 307             if(string.charAt(i) != WHSP) {
 308                 return i + 1;
 309             }
 310         }
 311         return 0;
 312     }
 313 
 314     final private static void skipWhitespace(String string, int[] pos) {
 315         for(int i=pos[0]; i < string.length(); i++) {
 316             if(string.charAt(i) != WHSP) {
 317                 pos[0] = i;
 318                 if (debug) {
 319                     System.err.println("skipWhitespace: skipping to "+i);
 320                 }
 321                 return;
 322             }
 323         }
 324     }
 325 
 326     final private static Attribute readNumericOID(String string, int[] pos)
 327         throws NamingException {
 328 
 329         if (debug) {
 330             System.err.println("readNumericoid: pos="+pos[0]);
 331         }
 332 
 333         int begin, end;
 334         String value = null;
 335 
 336         skipWhitespace(string, pos);
 337 
 338         begin = pos[0];
 339         end = string.indexOf(WHSP, begin);
 340 
 341         if (end == -1 || end - begin < 1) {
 342             throw new InvalidAttributeValueException("no numericoid found: "
 343                                                      + string);
 344         }
 345 
 346         value = string.substring(begin, end);
 347 
 348         pos[0] += value.length();
 349 
 350         return new BasicAttribute(NUMERICOID_ID, value);
 351     }
 352 
 353     final private static Attribute readNextTag(String string, int[] pos)
 354         throws NamingException {
 355 
 356         Attribute       attr = null;
 357         String          tagName = null;
 358         String[]        values = null;
 359 
 360         skipWhitespace(string, pos);
 361 
 362         if (debug) {
 363             System.err.println("readNextTag: pos="+pos[0]);
 364         }
 365 
 366         // get the name and values of the attribute to return
 367         int trailingSpace = string.indexOf( WHSP, pos[0] );
 368 
 369         // tolerate a schema that omits the trailing space
 370         if (trailingSpace < 0) {
 371             tagName = string.substring( pos[0], string.length() - 1);
 372         } else {
 373             tagName = string.substring( pos[0], trailingSpace );
 374         }
 375 
 376         values = readTag(tagName, string, pos);
 377 
 378         // make sure at least one value was returned
 379         if(values.length < 0) {
 380             throw new InvalidAttributeValueException("no values for " +
 381                                                      "attribute \"" +
 382                                                      tagName + "\"");
 383         }
 384 
 385         // create the attribute, using the first value
 386         attr = new BasicAttribute(tagName, values[0]);
 387 
 388         // add other values if there are any
 389         for(int i = 1; i < values.length; i++) {
 390             attr.add(values[i]);
 391         }
 392 
 393         return attr;
 394     }
 395 
 396     final private static String[] readTag(String tag, String string, int[] pos)
 397         throws NamingException {
 398 
 399         if (debug) {
 400             System.err.println("ReadTag: " + tag + " pos="+pos[0]);
 401         }
 402 
 403         // move parser past tag name
 404         pos[0] += tag.length();
 405         skipWhitespace(string, pos);
 406 
 407         if (tag.equals(NAME_ID)) {
 408             return readQDescrs(string, pos);  // names[0] is NAME
 409         }
 410 
 411         if(tag.equals(DESC_ID)) {
 412            return readQDString(string, pos);
 413         }
 414 
 415         if (
 416            tag.equals(EQUALITY_ID) ||
 417            tag.equals(ORDERING_ID) ||
 418            tag.equals(SUBSTR_ID) ||
 419            tag.equals(SYNTAX_ID)) {
 420             return readWOID(string, pos);
 421         }
 422 
 423         if (tag.equals(OBSOLETE_ID) ||
 424             tag.equals(ABSTRACT_ID) ||
 425             tag.equals(STRUCTURAL_ID) ||
 426             tag.equals(AUXILIARY_ID) ||
 427             tag.equals(SINGLE_VAL_ID) ||
 428             tag.equals(COLLECTIVE_ID) ||
 429             tag.equals(NO_USER_MOD_ID)) {
 430             return new String[] {SCHEMA_TRUE_VALUE};
 431         }
 432 
 433         if (tag.equals(SUP_ID) ||   // oid list for object class; WOID for attribute
 434             tag.equals(MUST_ID) ||
 435             tag.equals(MAY_ID) ||
 436             tag.equals(USAGE_ID)) {
 437             return readOIDs(string, pos);
 438         }
 439 
 440         // otherwise it's a schema element with a quoted string value
 441         return readQDStrings(string, pos);
 442     }
 443 
 444     final private static String[] readQDString(String string, int[] pos)
 445         throws NamingException {
 446 
 447         int begin, end;
 448 
 449         begin = string.indexOf(SINGLE_QUOTE, pos[0]) + 1;
 450         end = string.indexOf(SINGLE_QUOTE, begin);
 451 
 452         if (debug) {
 453             System.err.println("ReadQDString: pos=" + pos[0] +
 454                                " begin=" + begin + " end=" + end);
 455         }
 456 
 457         if(begin == -1 || end == -1 || begin == end) {
 458             throw new InvalidAttributeIdentifierException("malformed " +
 459                                                           "QDString: " +
 460                                                           string);
 461         }
 462 
 463         // make sure the qdstring end symbol is there
 464         if (string.charAt(begin - 1) != SINGLE_QUOTE) {
 465             throw new InvalidAttributeIdentifierException("qdstring has " +
 466                                                           "no end mark: " +
 467                                                           string);
 468         }
 469 
 470         pos[0] = end+1;
 471         return new String[] {string.substring(begin, end)};
 472     }
 473 
 474    /**
 475     * dstring         = 1*utf8
 476     * qdstring        = whsp "'" dstring "'" whsp
 477     * qdstringlist    = [ qdstring *( qdstring ) ]
 478     * qdstrings       = qdstring / ( whsp "(" qdstringlist ")" whsp )
 479     */
 480     private final static String[] readQDStrings(String string, int[] pos)
 481         throws NamingException {
 482 
 483         return readQDescrs(string, pos);
 484     }
 485 
 486     /**
 487      * ; object descriptors used as schema element names
 488      * qdescrs         = qdescr / ( whsp "(" qdescrlist ")" whsp )
 489      * qdescrlist      = [ qdescr *( qdescr ) ]
 490      * qdescr          = whsp "'" descr "'" whsp
 491      * descr           = keystring
 492      */
 493     final private static String[] readQDescrs(String string, int[] pos)
 494         throws NamingException {
 495 
 496         if (debug) {
 497             System.err.println("readQDescrs: pos="+pos[0]);
 498         }
 499 
 500         skipWhitespace(string, pos);
 501 
 502         switch( string.charAt(pos[0]) ) {
 503         case OID_LIST_BEGIN:
 504             return readQDescrList(string, pos);
 505         case SINGLE_QUOTE:
 506             return readQDString(string, pos);
 507         default:
 508             throw new InvalidAttributeValueException("unexpected oids " +
 509                                                      "string: " + string);
 510         }
 511     }
 512 
 513     /**
 514      * qdescrlist      = [ qdescr *( qdescr ) ]
 515      * qdescr          = whsp "'" descr "'" whsp
 516      * descr           = keystring
 517      */
 518     final private static String[] readQDescrList(String string, int[] pos)
 519         throws NamingException {
 520 
 521         int begin, end;
 522         Vector<String> values = new Vector<>(5);
 523 
 524         if (debug) {
 525             System.err.println("ReadQDescrList: pos="+pos[0]);
 526         }
 527 
 528         pos[0]++; // skip '('
 529         skipWhitespace(string, pos);
 530         begin = pos[0];
 531         end = string.indexOf(OID_LIST_END, begin);
 532 
 533         if(end == -1) {
 534             throw new InvalidAttributeValueException ("oidlist has no end "+
 535                                                       "mark: " + string);
 536         }
 537 
 538         while(begin < end) {
 539             String[] one = readQDString(string,  pos);
 540 
 541             if (debug) {
 542                 System.err.println("ReadQDescrList: found '" + one[0] +
 543                                    "' at begin=" + begin + " end =" + end);
 544             }
 545 
 546             values.addElement(one[0]);
 547             skipWhitespace(string, pos);
 548             begin = pos[0];
 549         }
 550 
 551         pos[0] = end+1; // skip ')'
 552 
 553         String[] answer = new String[values.size()];
 554         for (int i = 0; i < answer.length; i++) {
 555             answer[i] = values.elementAt(i);
 556         }
 557         return answer;
 558     }
 559 
 560     final private static String[] readWOID(String string, int[] pos)
 561         throws NamingException {
 562 
 563         if (debug) {
 564             System.err.println("readWOIDs: pos="+pos[0]);
 565         }
 566 
 567         skipWhitespace(string, pos);
 568 
 569         if (string.charAt(pos[0]) == SINGLE_QUOTE) {
 570             // %%% workaround for Netscape schema bug
 571             return readQDString(string, pos);
 572         }
 573 
 574         int begin, end;
 575 
 576         begin = pos[0];
 577         end = string.indexOf(WHSP, begin);
 578 
 579         if (debug) {
 580             System.err.println("ReadWOID: pos=" + pos[0] +
 581                                " begin=" + begin + " end=" + end);
 582         }
 583 
 584         if(end == -1 || begin == end) {
 585             throw new InvalidAttributeIdentifierException("malformed " +
 586                                                           "OID: " +
 587                                                           string);
 588         }
 589         pos[0] = end+1;
 590 
 591         return new String[] {string.substring(begin, end)};
 592     }
 593 
 594     /*
 595      * oids            = woid / ( "(" oidlist ")" )
 596      * oidlist         = woid *( "$" woid )
 597      */
 598     final private static String[] readOIDs(String string, int[] pos)
 599         throws NamingException {
 600 
 601         if (debug) {
 602             System.err.println("readOIDs: pos="+pos[0]);
 603         }
 604 
 605         skipWhitespace(string, pos);
 606 
 607         // Single OID
 608         if (string.charAt(pos[0]) != OID_LIST_BEGIN) {
 609             return readWOID(string, pos);
 610         }
 611 
 612         // Multiple OIDs
 613 
 614         int     begin, cur, end;
 615         String  oidName = null;
 616         Vector<String> values = new Vector<>(5);
 617 
 618         if (debug) {
 619             System.err.println("ReadOIDList: pos="+pos[0]);
 620         }
 621 
 622         pos[0]++;
 623         skipWhitespace(string, pos);
 624         begin = pos[0];
 625         end = string.indexOf(OID_LIST_END, begin);
 626         cur = string.indexOf(OID_SEPARATOR, begin);
 627 
 628         if(end == -1) {
 629             throw new InvalidAttributeValueException ("oidlist has no end "+
 630                                                       "mark: " + string);
 631         }
 632 
 633         if(cur == -1 || end < cur) {
 634             cur = end;
 635         }
 636 
 637         while(cur < end && cur > 0) {
 638             int wsBegin = findTrailingWhitespace(string, cur - 1);
 639             oidName = string.substring(begin, wsBegin);
 640             if (debug) {
 641                 System.err.println("ReadOIDList: found '" + oidName +
 642                                    "' at begin=" + begin + " end =" + end);
 643             }
 644             values.addElement(oidName);
 645             pos[0] = cur + 1;
 646             skipWhitespace(string, pos);
 647             begin = pos[0];
 648             cur = string.indexOf(OID_SEPARATOR, begin);
 649             if(debug) {System.err.println("ReadOIDList: begin = " + begin);}
 650         }
 651 
 652         if (debug) {
 653             System.err.println("ReadOIDList: found '" + oidName +
 654                                "' at begin=" + begin + " end =" + end);
 655         }
 656 
 657         int wsBegin = findTrailingWhitespace(string, end - 1);
 658         oidName = string.substring(begin, wsBegin);
 659         values.addElement(oidName);
 660 
 661         pos[0] = end+1;
 662 
 663         String[] answer = new String[values.size()];
 664         for (int i = 0; i < answer.length; i++) {
 665             answer[i] = values.elementAt(i);
 666         }
 667         return answer;
 668     }
 669 
 670 // ----------------- "unparser" methods
 671 // Methods that are used for translating a node in the schema tree
 672 // into RFC2252 format for storage back into the LDAP directory
 673 /*
 674      static Attributes JNDI2LDAPSchema(DirContext schemaRoot)
 675         throws NamingException {
 676 
 677         Attribute objDescAttr = new BasicAttribute(OBJECTCLASSDESC_ATTR_ID);
 678         Attribute attrDescAttr = new BasicAttribute(ATTRIBUTEDESC_ATTR_ID);
 679         Attribute syntaxDescAttr = new BasicAttribute(SYNTAXDESC_ATTR_ID);
 680         Attributes attrs = new BasicAttributes(LdapClient.caseIgnore);
 681         DirContext classDefs, attributeDefs, syntaxDefs;
 682         Attributes classDefsAttrs, attributeDefsAttrs, syntaxDefsAttrs;
 683         NamingEnumeration defs;
 684         Object obj;
 685         int i = 0;
 686 
 687         try {
 688             obj = schemaRoot.lookup(OBJECTCLASS_DEFINITION_NAME);
 689             if(obj != null && obj instanceof DirContext) {
 690                 classDefs = (DirContext)obj;
 691                 defs = classDefs.listBindings("");
 692                 while(defs.hasMoreElements()) {
 693                     i++;
 694                     DirContext classDef = (DirContext)
 695                         ((Binding)(defs.next())).getObject();
 696                     classDefAttrs = classDef.getAttributes("");
 697                     objDescAttr.add(classDef2ObjectDesc(classDefAttrs));
 698                 }
 699                 if (debug)
 700                     System.err.println(i + " total object classes");
 701                 attrs.put(objDescAttr);
 702             } else {
 703                 throw new NamingException(
 704                     "Problem with Schema tree: the object named " +
 705                     OBJECTCLASS_DEFINITION_NAME + " is not a " +
 706                     "DirContext");
 707             }
 708         } catch (NameNotFoundException e) {} // ignore
 709 
 710         i=0;
 711         try {
 712             obj = schemaRoot.lookup(ATTRIBUTE_DEFINITION_NAME);
 713             if(obj instanceof DirContext) {
 714                 attributeDefs = (DirContext)obj;
 715                 defs = attributeDefs.listBindings("");
 716                 while(defs.hasMoreElements()) {
 717                     i++;
 718                     DirContext attrDef = (DirContext)
 719                         ((Binding)defs.next()).getObject();
 720                     attrDefAttrs = attrDef.getAttributes("");
 721                     attrDescAttr.add(attrDef2AttrDesc(attrDefAttrs));
 722                 }
 723                 if (debug)
 724                     System.err.println(i + " attribute definitions");
 725                 attrs.put(attrDescAttr);
 726             } else {
 727                 throw new NamingException(
 728                     "Problem with schema tree: the object named " +
 729                     ATTRIBUTE_DEFINITION_NAME + " is not a " +
 730                     "DirContext");
 731             }
 732         } catch (NameNotFoundException e) {} // ignore
 733 
 734         i=0;
 735         try {
 736             obj = schemaRoot.lookup(SYNTAX_DEFINITION_NAME);
 737             if(obj instanceof DirContext) {
 738                 syntaxDefs = (DirContext)obj;
 739                 defs =syntaxDefs.listBindings("");
 740                 while(defs.hasMoreElements()) {
 741                     i++;
 742                     DirContext syntaxDef = (DirContext)
 743                         ((Binding)defs.next()).getObject();
 744                     syntaxDefAttrs = syntaxDef.getAttributes("");
 745                     syntaxDescAttr.add(syntaxDef2SyntaxDesc(syntaxDefAttrs));
 746                 }
 747                 if (debug)
 748                     System.err.println(i + " total syntax definitions");
 749                 attrs.put(syntaxDescAttr);
 750             } else {
 751                 throw new NamingException(
 752                     "Problem with schema tree: the object named " +
 753                     SYNTAX_DEFINITION_NAME + " is not a " +
 754                     "DirContext");
 755             }
 756         } catch (NameNotFoundException e) {} // ignore
 757 
 758         return attrs;
 759     }
 760 
 761 */
 762 
 763     /**
 764       * Translate attributes that describe an object class into the
 765       * string description as defined in RFC 2252.
 766       */
 767     final private String classDef2ObjectDesc(Attributes attrs)
 768         throws NamingException {
 769 
 770         StringBuilder objectDesc = new StringBuilder("( ");
 771 
 772         Attribute attr = null;
 773         int count = 0;
 774 
 775         // extract attributes by ID to guarantee ordering
 776 
 777         attr = attrs.get(NUMERICOID_ID);
 778         if (attr != null) {
 779             objectDesc.append(writeNumericOID(attr));
 780             count++;
 781         } else {
 782             throw new ConfigurationException("Class definition doesn't" +
 783                                              "have a numeric OID");
 784         }
 785 
 786         attr = attrs.get(NAME_ID);
 787         if (attr != null) {
 788             objectDesc.append(writeQDescrs(attr));
 789             count++;
 790         }
 791 
 792         attr = attrs.get(DESC_ID);
 793         if (attr != null) {
 794             objectDesc.append(writeQDString(attr));
 795             count++;
 796         }
 797 
 798         attr = attrs.get(OBSOLETE_ID);
 799         if (attr != null) {
 800             objectDesc.append(writeBoolean(attr));
 801             count++;
 802         }
 803 
 804         attr = attrs.get(SUP_ID);
 805         if (attr != null) {
 806             objectDesc.append(writeOIDs(attr));
 807             count++;
 808         }
 809 
 810         attr = attrs.get(ABSTRACT_ID);
 811         if (attr != null) {
 812             objectDesc.append(writeBoolean(attr));
 813             count++;
 814         }
 815 
 816         attr = attrs.get(STRUCTURAL_ID);
 817         if (attr != null) {
 818             objectDesc.append(writeBoolean(attr));
 819             count++;
 820         }
 821 
 822         attr = attrs.get(AUXILIARY_ID);
 823         if (attr != null) {
 824             objectDesc.append(writeBoolean(attr));
 825             count++;
 826         }
 827 
 828         attr = attrs.get(MUST_ID);
 829         if (attr != null) {
 830             objectDesc.append(writeOIDs(attr));
 831             count++;
 832         }
 833 
 834         attr = attrs.get(MAY_ID);
 835         if (attr != null) {
 836             objectDesc.append(writeOIDs(attr));
 837             count++;
 838         }
 839 
 840         // process any remaining attributes
 841         if (count < attrs.size()) {
 842             String attrId = null;
 843 
 844             // use enumeration because attribute ID is not known
 845             for (NamingEnumeration<? extends Attribute> ae = attrs.getAll();
 846                 ae.hasMoreElements(); ) {
 847 
 848                 attr = ae.next();
 849                 attrId = attr.getID();
 850 
 851                 // skip those already processed
 852                 if (attrId.equals(NUMERICOID_ID) ||
 853                     attrId.equals(NAME_ID) ||
 854                     attrId.equals(SUP_ID) ||
 855                     attrId.equals(MAY_ID) ||
 856                     attrId.equals(MUST_ID) ||
 857                     attrId.equals(STRUCTURAL_ID) ||
 858                     attrId.equals(DESC_ID) ||
 859                     attrId.equals(AUXILIARY_ID) ||
 860                     attrId.equals(ABSTRACT_ID) ||
 861                     attrId.equals(OBSOLETE_ID)) {
 862                     continue;
 863 
 864                 } else {
 865                     objectDesc.append(writeQDStrings(attr));
 866                 }
 867             }
 868         }
 869 
 870         objectDesc.append(")");
 871 
 872         return objectDesc.toString();
 873     }
 874 
 875     /**
 876       * Translate attributes that describe an attribute definition into the
 877       * string description as defined in RFC 2252.
 878       */
 879     final private String attrDef2AttrDesc(Attributes attrs)
 880         throws NamingException {
 881 
 882         StringBuilder attrDesc = new StringBuilder("( "); // opening parens
 883 
 884         Attribute attr = null;
 885         int count = 0;
 886 
 887         // extract attributes by ID to guarantee ordering
 888 
 889         attr = attrs.get(NUMERICOID_ID);
 890         if (attr != null) {
 891             attrDesc.append(writeNumericOID(attr));
 892             count++;
 893         } else {
 894             throw new ConfigurationException("Attribute type doesn't" +
 895                                              "have a numeric OID");
 896         }
 897 
 898         attr = attrs.get(NAME_ID);
 899         if (attr != null) {
 900             attrDesc.append(writeQDescrs(attr));
 901             count++;
 902         }
 903 
 904         attr = attrs.get(DESC_ID);
 905         if (attr != null) {
 906             attrDesc.append(writeQDString(attr));
 907             count++;
 908         }
 909 
 910         attr = attrs.get(OBSOLETE_ID);
 911         if (attr != null) {
 912             attrDesc.append(writeBoolean(attr));
 913             count++;
 914         }
 915 
 916         attr = attrs.get(SUP_ID);
 917         if (attr != null) {
 918             attrDesc.append(writeWOID(attr));
 919             count++;
 920         }
 921 
 922         attr = attrs.get(EQUALITY_ID);
 923         if (attr != null) {
 924             attrDesc.append(writeWOID(attr));
 925             count++;
 926         }
 927 
 928         attr = attrs.get(ORDERING_ID);
 929         if (attr != null) {
 930             attrDesc.append(writeWOID(attr));
 931             count++;
 932         }
 933 
 934         attr = attrs.get(SUBSTR_ID);
 935         if (attr != null) {
 936             attrDesc.append(writeWOID(attr));
 937             count++;
 938         }
 939 
 940         attr = attrs.get(SYNTAX_ID);
 941         if (attr != null) {
 942             attrDesc.append(writeWOID(attr));
 943             count++;
 944         }
 945 
 946         attr = attrs.get(SINGLE_VAL_ID);
 947         if (attr != null) {
 948             attrDesc.append(writeBoolean(attr));
 949             count++;
 950         }
 951 
 952         attr = attrs.get(COLLECTIVE_ID);
 953         if (attr != null) {
 954             attrDesc.append(writeBoolean(attr));
 955             count++;
 956         }
 957 
 958         attr = attrs.get(NO_USER_MOD_ID);
 959         if (attr != null) {
 960             attrDesc.append(writeBoolean(attr));
 961             count++;
 962         }
 963 
 964         attr = attrs.get(USAGE_ID);
 965         if (attr != null) {
 966             attrDesc.append(writeQDString(attr));
 967             count++;
 968         }
 969 
 970         // process any remaining attributes
 971         if (count < attrs.size()) {
 972             String attrId = null;
 973 
 974             // use enumeration because attribute ID is not known
 975             for (NamingEnumeration<? extends Attribute> ae = attrs.getAll();
 976                 ae.hasMoreElements(); ) {
 977 
 978                 attr = ae.next();
 979                 attrId = attr.getID();
 980 
 981                 // skip those already processed
 982                 if (attrId.equals(NUMERICOID_ID) ||
 983                     attrId.equals(NAME_ID) ||
 984                     attrId.equals(SYNTAX_ID) ||
 985                     attrId.equals(DESC_ID) ||
 986                     attrId.equals(SINGLE_VAL_ID) ||
 987                     attrId.equals(EQUALITY_ID) ||
 988                     attrId.equals(ORDERING_ID) ||
 989                     attrId.equals(SUBSTR_ID) ||
 990                     attrId.equals(NO_USER_MOD_ID) ||
 991                     attrId.equals(USAGE_ID) ||
 992                     attrId.equals(SUP_ID) ||
 993                     attrId.equals(COLLECTIVE_ID) ||
 994                     attrId.equals(OBSOLETE_ID)) {
 995                     continue;
 996 
 997                 } else {
 998                     attrDesc.append(writeQDStrings(attr));
 999                 }
1000             }
1001         }
1002 
1003         attrDesc.append(")");  // add closing parens
1004 
1005         return attrDesc.toString();
1006     }
1007 
1008     /**
1009       * Translate attributes that describe an attribute syntax definition into the
1010       * string description as defined in RFC 2252.
1011       */
1012     final private String syntaxDef2SyntaxDesc(Attributes attrs)
1013         throws NamingException {
1014 
1015         StringBuilder syntaxDesc = new StringBuilder("( "); // opening parens
1016 
1017         Attribute attr = null;
1018         int count = 0;
1019 
1020         // extract attributes by ID to guarantee ordering
1021 
1022         attr = attrs.get(NUMERICOID_ID);
1023         if (attr != null) {
1024             syntaxDesc.append(writeNumericOID(attr));
1025             count++;
1026         } else {
1027             throw new ConfigurationException("Attribute type doesn't" +
1028                                              "have a numeric OID");
1029         }
1030 
1031         attr = attrs.get(DESC_ID);
1032         if (attr != null) {
1033             syntaxDesc.append(writeQDString(attr));
1034             count++;
1035         }
1036 
1037         // process any remaining attributes
1038         if (count < attrs.size()) {
1039             String attrId = null;
1040 
1041             // use enumeration because attribute ID is not known
1042             for (NamingEnumeration<? extends Attribute> ae = attrs.getAll();
1043                 ae.hasMoreElements(); ) {
1044 
1045                 attr = ae.next();
1046                 attrId = attr.getID();
1047 
1048                 // skip those already processed
1049                 if (attrId.equals(NUMERICOID_ID) ||
1050                     attrId.equals(DESC_ID)) {
1051                     continue;
1052 
1053                 } else {
1054                     syntaxDesc.append(writeQDStrings(attr));
1055                 }
1056             }
1057         }
1058 
1059         syntaxDesc.append(")");
1060 
1061         return syntaxDesc.toString();
1062     }
1063 
1064     /**
1065       * Translate attributes that describe an attribute matching rule
1066       * definition into the string description as defined in RFC 2252.
1067       */
1068     final private String matchRuleDef2MatchRuleDesc(Attributes attrs)
1069         throws NamingException {
1070 
1071         StringBuilder matchRuleDesc = new StringBuilder("( "); // opening parens
1072 
1073         Attribute attr = null;
1074         int count = 0;
1075 
1076         // extract attributes by ID to guarantee ordering
1077 
1078         attr = attrs.get(NUMERICOID_ID);
1079         if (attr != null) {
1080             matchRuleDesc.append(writeNumericOID(attr));
1081             count++;
1082         } else {
1083             throw new ConfigurationException("Attribute type doesn't" +
1084                                              "have a numeric OID");
1085         }
1086 
1087         attr = attrs.get(NAME_ID);
1088         if (attr != null) {
1089             matchRuleDesc.append(writeQDescrs(attr));
1090             count++;
1091         }
1092 
1093         attr = attrs.get(DESC_ID);
1094         if (attr != null) {
1095             matchRuleDesc.append(writeQDString(attr));
1096             count++;
1097         }
1098 
1099         attr = attrs.get(OBSOLETE_ID);
1100         if (attr != null) {
1101             matchRuleDesc.append(writeBoolean(attr));
1102             count++;
1103         }
1104 
1105         attr = attrs.get(SYNTAX_ID);
1106         if (attr != null) {
1107             matchRuleDesc.append(writeWOID(attr));
1108             count++;
1109         } else {
1110             throw new ConfigurationException("Attribute type doesn't" +
1111                                              "have a syntax OID");
1112         }
1113 
1114         // process any remaining attributes
1115         if (count < attrs.size()) {
1116             String attrId = null;
1117 
1118             // use enumeration because attribute ID is not known
1119             for (NamingEnumeration<? extends Attribute> ae = attrs.getAll();
1120                 ae.hasMoreElements(); ) {
1121 
1122                 attr = ae.next();
1123                 attrId = attr.getID();
1124 
1125                 // skip those already processed
1126                 if (attrId.equals(NUMERICOID_ID) ||
1127                     attrId.equals(NAME_ID) ||
1128                     attrId.equals(SYNTAX_ID) ||
1129                     attrId.equals(DESC_ID) ||
1130                     attrId.equals(OBSOLETE_ID)) {
1131                     continue;
1132 
1133                 } else {
1134                     matchRuleDesc.append(writeQDStrings(attr));
1135                 }
1136             }
1137         }
1138 
1139         matchRuleDesc.append(")");
1140 
1141         return matchRuleDesc.toString();
1142     }
1143 
1144     final private String writeNumericOID(Attribute nOIDAttr)
1145         throws NamingException {
1146         if(nOIDAttr.size() != 1) {
1147             throw new InvalidAttributeValueException(
1148                 "A class definition must have exactly one numeric OID");
1149         }
1150         return (String)(nOIDAttr.get()) + WHSP;
1151     }
1152 
1153     final private String writeWOID(Attribute attr) throws NamingException {
1154         if (netscapeBug)
1155             return writeQDString(attr);
1156         else
1157             return attr.getID() + WHSP + attr.get() + WHSP;
1158     }
1159 
1160     /*  qdescr          = whsp "'" descr "'" whsp */
1161     final private String writeQDString(Attribute qdStringAttr)
1162         throws NamingException {
1163         if(qdStringAttr.size() != 1) {
1164             throw new InvalidAttributeValueException(
1165                 qdStringAttr.getID() + " must have exactly one value");
1166         }
1167 
1168         return qdStringAttr.getID() + WHSP +
1169             SINGLE_QUOTE + qdStringAttr.get() + SINGLE_QUOTE + WHSP;
1170     }
1171 
1172    /**
1173     * dstring         = 1*utf8
1174     * qdstring        = whsp "'" dstring "'" whsp
1175     * qdstringlist    = [ qdstring *( qdstring ) ]
1176     * qdstrings       = qdstring / ( whsp "(" qdstringlist ")" whsp )
1177     */
1178     private final String writeQDStrings(Attribute attr) throws NamingException {
1179         return writeQDescrs(attr);
1180     }
1181 
1182     /**
1183      * qdescrs         = qdescr / ( whsp "(" qdescrlist ")" whsp )
1184      * qdescrlist      = [ qdescr *( qdescr ) ]
1185      * qdescr          = whsp "'" descr "'" whsp
1186      * descr           = keystring
1187      */
1188     private final String writeQDescrs(Attribute attr) throws NamingException {
1189         switch(attr.size()) {
1190         case 0:
1191             throw new InvalidAttributeValueException(
1192                 attr.getID() + "has no values");
1193         case 1:
1194             return writeQDString(attr);
1195         }
1196 
1197         // write QDList
1198 
1199         StringBuilder qdList = new StringBuilder(attr.getID());
1200         qdList.append(WHSP);
1201         qdList.append(OID_LIST_BEGIN);
1202 
1203         NamingEnumeration<?> values = attr.getAll();
1204 
1205         while(values.hasMore()) {
1206             qdList.append(WHSP);
1207             qdList.append(SINGLE_QUOTE);
1208             qdList.append((String)values.next());
1209             qdList.append(SINGLE_QUOTE);
1210             qdList.append(WHSP);
1211         }
1212 
1213         qdList.append(OID_LIST_END);
1214         qdList.append(WHSP);
1215 
1216         return qdList.toString();
1217     }
1218 
1219     final private String writeOIDs(Attribute oidsAttr)
1220         throws NamingException {
1221 
1222         switch(oidsAttr.size()) {
1223         case 0:
1224             throw new InvalidAttributeValueException(
1225                 oidsAttr.getID() + "has no values");
1226 
1227         case 1:
1228             if (netscapeBug) {
1229                 break; // %%% write out as list to avoid crashing server
1230             }
1231             return writeWOID(oidsAttr);
1232         }
1233 
1234         // write OID List
1235 
1236         StringBuilder oidList = new StringBuilder(oidsAttr.getID());
1237         oidList.append(WHSP);
1238         oidList.append(OID_LIST_BEGIN);
1239 
1240         NamingEnumeration<?> values = oidsAttr.getAll();
1241         oidList.append(WHSP);
1242         oidList.append(values.next());
1243 
1244         while(values.hasMore()) {
1245             oidList.append(WHSP);
1246             oidList.append(OID_SEPARATOR);
1247             oidList.append(WHSP);
1248             oidList.append((String)values.next());
1249         }
1250 
1251         oidList.append(WHSP);
1252         oidList.append(OID_LIST_END);
1253         oidList.append(WHSP);
1254 
1255         return oidList.toString();
1256     }
1257 
1258     private final String writeBoolean(Attribute booleanAttr)
1259         throws NamingException {
1260             return booleanAttr.getID() + WHSP;
1261     }
1262 
1263     /**
1264      * Returns an attribute for updating the Object Class Definition schema
1265      * attribute
1266      */
1267     final Attribute stringifyObjDesc(Attributes classDefAttrs)
1268         throws NamingException {
1269         Attribute objDescAttr = new BasicAttribute(OBJECTCLASSDESC_ATTR_ID);
1270         objDescAttr.add(classDef2ObjectDesc(classDefAttrs));
1271         return objDescAttr;
1272     }
1273 
1274     /**
1275      * Returns an attribute for updating the Attribute Definition schema attribute
1276      */
1277     final Attribute stringifyAttrDesc(Attributes attrDefAttrs)
1278         throws NamingException {
1279         Attribute attrDescAttr = new BasicAttribute(ATTRIBUTEDESC_ATTR_ID);
1280         attrDescAttr.add(attrDef2AttrDesc(attrDefAttrs));
1281         return attrDescAttr;
1282     }
1283 
1284     /**
1285      * Returns an attribute for updating the Syntax schema attribute
1286      */
1287     final Attribute stringifySyntaxDesc(Attributes syntaxDefAttrs)
1288     throws NamingException {
1289         Attribute syntaxDescAttr = new BasicAttribute(SYNTAXDESC_ATTR_ID);
1290         syntaxDescAttr.add(syntaxDef2SyntaxDesc(syntaxDefAttrs));
1291         return syntaxDescAttr;
1292     }
1293 
1294     /**
1295      * Returns an attribute for updating the Matching Rule schema attribute
1296      */
1297     final Attribute stringifyMatchRuleDesc(Attributes matchRuleDefAttrs)
1298     throws NamingException {
1299         Attribute matchRuleDescAttr = new BasicAttribute(MATCHRULEDESC_ATTR_ID);
1300         matchRuleDescAttr.add(matchRuleDef2MatchRuleDesc(matchRuleDefAttrs));
1301         return matchRuleDescAttr;
1302     }
1303 }