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