1 /*
   2  * Copyright (c) 1999, 2003, 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.Hashtable;
  31 import com.sun.jndi.toolkit.dir.HierMemDirCtx;
  32 
  33 /**
  34  * This is the class used to implement LDAP's GetSchema call.
  35  *
  36  * It subclasses HierMemDirContext for most of the functionality. It
  37  * overrides functions that cause the schema definitions to change.
  38  * In such a case, it write the schema to the LdapServer and (assuming
  39  * there are no errors), calls it's superclass's equivalent function.
  40  * Thus, the schema tree and the LDAP server's schema attributes are
  41  * always in sync.
  42  */
  43 
  44 final class LdapSchemaCtx extends HierMemDirCtx {
  45 
  46     static private final boolean debug = false;
  47 
  48     private static final int LEAF = 0;  // schema object (e.g. attribute type defn)
  49     private static final int SCHEMA_ROOT = 1;   // schema tree root
  50     static final int OBJECTCLASS_ROOT = 2;   // root of object class subtree
  51     static final int ATTRIBUTE_ROOT = 3;     // root of attribute type subtree
  52     static final int SYNTAX_ROOT = 4;        // root of syntax subtree
  53     static final int MATCHRULE_ROOT = 5;     // root of matching rule subtree
  54     static final int OBJECTCLASS = 6;   // an object class definition
  55     static final int ATTRIBUTE = 7;     // an attribute type definition
  56     static final int SYNTAX = 8;        // a syntax definition
  57     static final int MATCHRULE = 9;     // a matching rule definition
  58 
  59     private SchemaInfo info= null;
  60     private boolean setupMode = true;
  61 
  62     private int objectType;
  63 
  64     static DirContext createSchemaTree(Hashtable env, String subschemasubentry,
  65         LdapCtx schemaEntry, Attributes schemaAttrs, boolean netscapeBug)
  66         throws NamingException {
  67             try {
  68                 LdapSchemaParser parser = new LdapSchemaParser(netscapeBug);
  69 
  70                 SchemaInfo allinfo = new SchemaInfo(subschemasubentry,
  71                     schemaEntry, parser);
  72 
  73                 LdapSchemaCtx root = new LdapSchemaCtx(SCHEMA_ROOT, env, allinfo);
  74                 parser.LDAP2JNDISchema(schemaAttrs, root);
  75                 return root;
  76             } catch (NamingException e) {
  77                 schemaEntry.close(); // cleanup
  78                 throw e;
  79             }
  80     }
  81 
  82     // Called by createNewCtx
  83     private LdapSchemaCtx(int objectType, Hashtable environment, SchemaInfo info) {
  84         super(environment, LdapClient.caseIgnore);
  85 
  86         this.objectType = objectType;
  87         this.info = info;
  88     }
  89 
  90     // override HierMemDirCtx.close to prevent premature GC of shared data
  91     public void close() throws NamingException {
  92         info.close();
  93     }
  94 
  95     // override to ignore obj and use attrs
  96     // treat same as createSubcontext
  97     final public void bind(Name name, Object obj, Attributes attrs)
  98         throws NamingException {
  99         if (!setupMode) {
 100             if (obj != null) {
 101                 throw new IllegalArgumentException("obj must be null");
 102             }
 103 
 104             // Update server
 105             addServerSchema(attrs);
 106         }
 107 
 108         // Update in-memory copy
 109         LdapSchemaCtx newEntry =
 110             (LdapSchemaCtx)super.doCreateSubcontext(name, attrs);
 111     }
 112 
 113     final protected void doBind(Name name, Object obj, Attributes attrs,
 114         boolean useFactory) throws NamingException {
 115         if (!setupMode) {
 116             throw new SchemaViolationException(
 117                 "Cannot bind arbitrary object; use createSubcontext()");
 118         } else {
 119             super.doBind(name, obj, attrs, false); // always ignore factories
 120         }
 121     }
 122 
 123     // override to use bind() instead
 124     final public void rebind(Name name, Object obj, Attributes attrs)
 125         throws NamingException {
 126         try {
 127             doLookup(name, false);
 128             throw new SchemaViolationException(
 129                 "Cannot replace existing schema object");
 130         } catch (NameNotFoundException e) {
 131             bind(name, obj, attrs);
 132         }
 133     }
 134 
 135     final protected void doRebind(Name name, Object obj, Attributes attrs,
 136         boolean useFactory) throws NamingException {
 137         if (!setupMode) {
 138             throw new SchemaViolationException(
 139                 "Cannot bind arbitrary object; use createSubcontext()");
 140         } else {
 141             super.doRebind(name, obj, attrs, false); // always ignore factories
 142         }
 143     }
 144 
 145     final protected void doUnbind(Name name) throws NamingException {
 146         if (!setupMode) {
 147             // Update server
 148             try {
 149                 // Lookup entry from memory
 150                 LdapSchemaCtx target = (LdapSchemaCtx)doLookup(name, false);
 151 
 152                 deleteServerSchema(target.attrs);
 153             } catch (NameNotFoundException e) {
 154                 return;
 155             }
 156         }
 157         // Update in-memory copy
 158         super.doUnbind(name);
 159     }
 160 
 161     final protected void doRename(Name oldname, Name newname)
 162         throws NamingException {
 163         if (!setupMode) {
 164             throw new SchemaViolationException("Cannot rename a schema object");
 165         } else {
 166             super.doRename(oldname, newname);
 167         }
 168     }
 169 
 170     final protected void doDestroySubcontext(Name name) throws NamingException {
 171         if (!setupMode) {
 172             // Update server
 173             try {
 174                 // Lookup entry from memory
 175                 LdapSchemaCtx target = (LdapSchemaCtx)doLookup(name, false);
 176 
 177                 deleteServerSchema(target.attrs);
 178             } catch (NameNotFoundException e) {
 179                 return;
 180             }
 181         }
 182 
 183         // Update in-memory copy
 184         super.doDestroySubcontext(name);
 185      }
 186 
 187     // Called to create oc, attr, syntax or matching rule roots and leaf entries
 188     final LdapSchemaCtx setup(int objectType, String name, Attributes attrs)
 189         throws NamingException{
 190             try {
 191                 setupMode = true;
 192                 LdapSchemaCtx answer =
 193                     (LdapSchemaCtx) super.doCreateSubcontext(
 194                         new CompositeName(name), attrs);
 195 
 196                 answer.objectType = objectType;
 197                 answer.setupMode = false;
 198                 return answer;
 199             } finally {
 200                 setupMode = false;
 201             }
 202     }
 203 
 204     final protected DirContext doCreateSubcontext(Name name, Attributes attrs)
 205         throws NamingException {
 206 
 207         if (attrs == null || attrs.size() == 0) {
 208             throw new SchemaViolationException(
 209                 "Must supply attributes describing schema");
 210         }
 211 
 212         if (!setupMode) {
 213             // Update server
 214             addServerSchema(attrs);
 215         }
 216 
 217         // Update in-memory copy
 218         LdapSchemaCtx newEntry =
 219             (LdapSchemaCtx) super.doCreateSubcontext(name, attrs);
 220         return newEntry;
 221     }
 222 
 223     final private static Attributes deepClone(Attributes orig)
 224         throws NamingException {
 225         BasicAttributes copy = new BasicAttributes(true);
 226         NamingEnumeration attrs = orig.getAll();
 227         while (attrs.hasMore()) {
 228             copy.put((Attribute)((Attribute)attrs.next()).clone());
 229         }
 230         return copy;
 231     }
 232 
 233     final protected void doModifyAttributes(ModificationItem[] mods)
 234         throws NamingException {
 235         if (setupMode) {
 236             super.doModifyAttributes(mods);
 237         } else {
 238             Attributes copy = deepClone(attrs);
 239 
 240             // Apply modifications to copy
 241             applyMods(mods, copy);
 242 
 243             // Update server copy
 244             modifyServerSchema(attrs, copy);
 245 
 246             // Update in-memory copy
 247             attrs = copy;
 248         }
 249     }
 250 
 251     // we override this so the superclass creates the right kind of contexts
 252     // Default is to create LEAF objects; caller will change after creation
 253     // if necessary
 254     final protected HierMemDirCtx createNewCtx() {
 255         LdapSchemaCtx ctx = new LdapSchemaCtx(LEAF, myEnv, info);
 256         return ctx;
 257     }
 258 
 259 
 260     final private void addServerSchema(Attributes attrs)
 261         throws NamingException {
 262         Attribute schemaAttr;
 263 
 264         switch (objectType) {
 265         case OBJECTCLASS_ROOT:
 266             schemaAttr = info.parser.stringifyObjDesc(attrs);
 267             break;
 268 
 269         case ATTRIBUTE_ROOT:
 270             schemaAttr = info.parser.stringifyAttrDesc(attrs);
 271             break;
 272 
 273         case SYNTAX_ROOT:
 274             schemaAttr = info.parser.stringifySyntaxDesc(attrs);
 275             break;
 276 
 277         case MATCHRULE_ROOT:
 278             schemaAttr = info.parser.stringifyMatchRuleDesc(attrs);
 279             break;
 280 
 281         case SCHEMA_ROOT:
 282             throw new SchemaViolationException(
 283                 "Cannot create new entry under schema root");
 284 
 285         default:
 286             throw new SchemaViolationException(
 287                 "Cannot create child of schema object");
 288         }
 289 
 290         Attributes holder = new BasicAttributes(true);
 291         holder.put(schemaAttr);
 292         //System.err.println((String)schemaAttr.get());
 293 
 294         info.modifyAttributes(myEnv, DirContext.ADD_ATTRIBUTE, holder);
 295 
 296     }
 297 
 298     /**
 299       * When we delete an entry, we use the original to make sure that
 300       * any formatting inconsistencies are eliminated.
 301       * This is because we're just deleting a value from an attribute
 302       * on the server and there might not be any checks for extra spaces
 303       * or parens.
 304       */
 305     final private void deleteServerSchema(Attributes origAttrs)
 306         throws NamingException {
 307 
 308         Attribute origAttrVal;
 309 
 310         switch (objectType) {
 311         case OBJECTCLASS_ROOT:
 312             origAttrVal = info.parser.stringifyObjDesc(origAttrs);
 313             break;
 314 
 315         case ATTRIBUTE_ROOT:
 316             origAttrVal = info.parser.stringifyAttrDesc(origAttrs);
 317             break;
 318 
 319         case SYNTAX_ROOT:
 320             origAttrVal = info.parser.stringifySyntaxDesc(origAttrs);
 321             break;
 322 
 323         case MATCHRULE_ROOT:
 324             origAttrVal = info.parser.stringifyMatchRuleDesc(origAttrs);
 325             break;
 326 
 327         case SCHEMA_ROOT:
 328             throw new SchemaViolationException(
 329                 "Cannot delete schema root");
 330 
 331         default:
 332             throw new SchemaViolationException(
 333                 "Cannot delete child of schema object");
 334         }
 335 
 336         ModificationItem[] mods = new ModificationItem[1];
 337         mods[0] = new ModificationItem(DirContext.REMOVE_ATTRIBUTE, origAttrVal);
 338 
 339         info.modifyAttributes(myEnv, mods);
 340     }
 341 
 342     /**
 343       * When we modify an entry, we use the original attribute value
 344       * in the schema to make sure that any formatting inconsistencies
 345       * are eliminated. A modification is done by deleting the original
 346       * value and adding a new value with the modification.
 347       */
 348     final private void modifyServerSchema(Attributes origAttrs,
 349         Attributes newAttrs) throws NamingException {
 350 
 351         Attribute newAttrVal;
 352         Attribute origAttrVal;
 353 
 354         switch (objectType) {
 355         case OBJECTCLASS:
 356             origAttrVal = info.parser.stringifyObjDesc(origAttrs);
 357             newAttrVal = info.parser.stringifyObjDesc(newAttrs);
 358             break;
 359 
 360         case ATTRIBUTE:
 361             origAttrVal = info.parser.stringifyAttrDesc(origAttrs);
 362             newAttrVal = info.parser.stringifyAttrDesc(newAttrs);
 363             break;
 364 
 365         case SYNTAX:
 366             origAttrVal = info.parser.stringifySyntaxDesc(origAttrs);
 367             newAttrVal = info.parser.stringifySyntaxDesc(newAttrs);
 368             break;
 369 
 370         case MATCHRULE:
 371             origAttrVal = info.parser.stringifyMatchRuleDesc(origAttrs);
 372             newAttrVal = info.parser.stringifyMatchRuleDesc(newAttrs);
 373             break;
 374 
 375         default:
 376             throw new SchemaViolationException(
 377                 "Cannot modify schema root");
 378         }
 379 
 380         ModificationItem[] mods = new ModificationItem[2];
 381         mods[0] = new ModificationItem(DirContext.REMOVE_ATTRIBUTE, origAttrVal);
 382         mods[1] = new ModificationItem(DirContext.ADD_ATTRIBUTE, newAttrVal);
 383 
 384         info.modifyAttributes(myEnv, mods);
 385     }
 386 
 387     final static private class SchemaInfo {
 388         private LdapCtx schemaEntry;
 389         private String schemaEntryName;
 390         LdapSchemaParser parser;
 391         private String host;
 392         private int port;
 393         private boolean hasLdapsScheme;
 394 
 395         SchemaInfo(String schemaEntryName, LdapCtx schemaEntry,
 396             LdapSchemaParser parser) {
 397             this.schemaEntryName = schemaEntryName;
 398             this.schemaEntry = schemaEntry;
 399             this.parser = parser;
 400             this.port = schemaEntry.port_number;
 401             this.host = schemaEntry.hostname;
 402             this.hasLdapsScheme = schemaEntry.hasLdapsScheme;
 403         }
 404 
 405         synchronized void close() throws NamingException {
 406             if (schemaEntry != null) {
 407                 schemaEntry.close();
 408                 schemaEntry = null;
 409             }
 410         }
 411 
 412         private LdapCtx reopenEntry(Hashtable env) throws NamingException {
 413             // Use subschemasubentry name as DN
 414             return new LdapCtx(schemaEntryName, host, port,
 415                                 env, hasLdapsScheme);
 416         }
 417 
 418         synchronized void modifyAttributes(Hashtable env, ModificationItem[] mods)
 419             throws NamingException {
 420             if (schemaEntry == null) {
 421                 schemaEntry = reopenEntry(env);
 422             }
 423             schemaEntry.modifyAttributes("", mods);
 424         }
 425 
 426         synchronized void modifyAttributes(Hashtable env, int mod,
 427             Attributes attrs) throws NamingException {
 428             if (schemaEntry == null) {
 429                 schemaEntry = reopenEntry(env);
 430             }
 431             schemaEntry.modifyAttributes("", mod, attrs);
 432         }
 433     }
 434 }