1 /*
2  * Copyright (c) 1999, 2017, 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 com.sun.jndi.toolkit.ctx.Continuation;
29 import java.util.NoSuchElementException;
30 import java.util.Vector;
31 
32 import javax.naming.*;
33 import javax.naming.directory.Attributes;
34 import javax.naming.ldap.Control;
35 
36 /**
37  * Basic enumeration for NameClassPair, Binding, and SearchResults.
38  */
39 
40 abstract class AbstractLdapNamingEnumeration<T extends NameClassPair>
41         implements NamingEnumeration<T>, ReferralEnumeration<T> {
42 
43     protected Name listArg;
44 
45     private boolean cleaned = false;
46     private LdapResult res;
47     private LdapClient enumClnt;
48     private Continuation cont;  // used to fill in exceptions
49     private Vector<LdapEntry> entries = null;
50     private int limit = 0;
51     private int posn = 0;
52     protected LdapCtx homeCtx;
53     private LdapReferralException refEx = null;
54     private NamingException errEx = null;
55 
56     /*
57      * Record the next set of entries and/or referrals.
58      */
AbstractLdapNamingEnumeration(LdapCtx homeCtx, LdapResult answer, Name listArg, Continuation cont)59     AbstractLdapNamingEnumeration(LdapCtx homeCtx, LdapResult answer, Name listArg,
60         Continuation cont) throws NamingException {
61 
62             // These checks are to accommodate referrals and limit exceptions
63             // which will generate an enumeration and defer the exception
64             // to be thrown at the end of the enumeration.
65             // All other exceptions are thrown immediately.
66             // Exceptions shouldn't be thrown here anyhow because
67             // process_return_code() is called before the constructor
68             // is called, so these are just safety checks.
69 
70             if ((answer.status != LdapClient.LDAP_SUCCESS) &&
71                 (answer.status != LdapClient.LDAP_SIZE_LIMIT_EXCEEDED) &&
72                 (answer.status != LdapClient.LDAP_TIME_LIMIT_EXCEEDED) &&
73                 (answer.status != LdapClient.LDAP_ADMIN_LIMIT_EXCEEDED) &&
74                 (answer.status != LdapClient.LDAP_REFERRAL) &&
75                 (answer.status != LdapClient.LDAP_PARTIAL_RESULTS)) {
76 
77                 // %%% need to deal with referral
78                 NamingException e = new NamingException(
79                                     LdapClient.getErrorMessage(
80                                     answer.status, answer.errorMessage));
81 
82                 throw cont.fillInException(e);
83             }
84 
85             // otherwise continue
86 
87             res = answer;
88             entries = answer.entries;
89             limit = (entries == null) ? 0 : entries.size(); // handle empty set
90             this.listArg = listArg;
91             this.cont = cont;
92 
93             if (answer.refEx != null) {
94                 refEx = answer.refEx;
95             }
96 
97             // Ensures that context won't get closed from underneath us
98             this.homeCtx = homeCtx;
99             homeCtx.incEnumCount();
100             enumClnt = homeCtx.clnt; // remember
101     }
102 
103     @Override
nextElement()104     public final T nextElement() {
105         try {
106             return next();
107         } catch (NamingException e) {
108             // can't throw exception
109             cleanup();
110             return null;
111         }
112     }
113 
114     @Override
hasMoreElements()115     public final boolean hasMoreElements() {
116         try {
117             return hasMore();
118         } catch (NamingException e) {
119             // can't throw exception
120             cleanup();
121             return false;
122         }
123     }
124 
125     /*
126      * Retrieve the next set of entries and/or referrals.
127      */
getNextBatch()128     private void getNextBatch() throws NamingException {
129 
130         res = homeCtx.getSearchReply(enumClnt, res);
131         if (res == null) {
132             limit = posn = 0;
133             return;
134         }
135 
136         entries = res.entries;
137         limit = (entries == null) ? 0 : entries.size(); // handle empty set
138         posn = 0; // reset
139 
140         // minimize the number of calls to processReturnCode()
141         // (expensive when batchSize is small and there are many results)
142         if ((res.status != LdapClient.LDAP_SUCCESS) ||
143             ((res.status == LdapClient.LDAP_SUCCESS) &&
144                 (res.referrals != null))) {
145 
146             try {
147                 // convert referrals into a chain of LdapReferralException
148                 homeCtx.processReturnCode(res, listArg);
149 
150             } catch (LimitExceededException | PartialResultException e) {
151                 setNamingException(e);
152 
153             }
154         }
155 
156         // merge any newly received referrals with any current referrals
157         if (res.refEx != null) {
158             if (refEx == null) {
159                 refEx = res.refEx;
160             } else {
161                 refEx = refEx.appendUnprocessedReferrals(res.refEx);
162             }
163             res.refEx = null; // reset
164         }
165 
166         if (res.resControls != null) {
167             homeCtx.respCtls = res.resControls;
168         }
169     }
170 
171     private boolean more = true;  // assume we have something to start with
172     private boolean hasMoreCalled = false;
173 
174     /*
175      * Test if unprocessed entries or referrals exist.
176      */
177     @Override
hasMore()178     public final boolean hasMore() throws NamingException {
179 
180         if (hasMoreCalled) {
181             return more;
182         }
183 
184         hasMoreCalled = true;
185 
186         if (!more) {
187             return false;
188         } else {
189             return (more = hasMoreImpl());
190         }
191     }
192 
193     /*
194      * Retrieve the next entry.
195      */
196     @Override
next()197     public final T next() throws NamingException {
198 
199         if (!hasMoreCalled) {
200             hasMore();
201         }
202         hasMoreCalled = false;
203         return nextImpl();
204     }
205 
206     /*
207      * Test if unprocessed entries or referrals exist.
208      */
hasMoreImpl()209     private boolean hasMoreImpl() throws NamingException {
210         // when page size is supported, this
211         // might generate an exception while attempting
212         // to fetch the next batch to determine
213         // whether there are any more elements
214 
215         // test if the current set of entries has been processed
216         if (posn == limit) {
217             getNextBatch();
218         }
219 
220         // test if any unprocessed entries exist
221         if (posn < limit) {
222             return true;
223         } else {
224 
225             try {
226                 // try to process another referral
227                 return hasMoreReferrals();
228 
229             } catch (LdapReferralException |
230                      LimitExceededException |
231                      PartialResultException e) {
232                 cleanup();
233                 throw e;
234 
235             } catch (NamingException e) {
236                 cleanup();
237                 PartialResultException pre = new PartialResultException();
238                 pre.setRootCause(e);
239                 throw pre;
240             }
241         }
242     }
243 
244     /*
245      * Retrieve the next entry.
246      */
nextImpl()247     private T nextImpl() throws NamingException {
248         try {
249             return nextAux();
250         } catch (NamingException e) {
251             cleanup();
252             throw cont.fillInException(e);
253         }
254     }
255 
nextAux()256     private T nextAux() throws NamingException {
257         if (posn == limit) {
258             getNextBatch();  // updates posn and limit
259         }
260 
261         if (posn >= limit) {
262             cleanup();
263             throw new NoSuchElementException("invalid enumeration handle");
264         }
265 
266         LdapEntry result = entries.elementAt(posn++);
267 
268         // gets and outputs DN from the entry
269         return createItem(result.DN, result.attributes, result.respCtls);
270     }
271 
getAtom(String dn)272     protected final String getAtom(String dn) {
273         // need to strip off all but lowest component of dn
274         // so that is relative to current context (currentDN)
275         try {
276             Name parsed = new LdapName(dn);
277             return parsed.get(parsed.size() - 1);
278         } catch (NamingException e) {
279             return dn;
280         }
281     }
282 
createItem(String dn, Attributes attrs, Vector<Control> respCtls)283     protected abstract T createItem(String dn, Attributes attrs,
284         Vector<Control> respCtls) throws NamingException;
285 
286     /*
287      * Append the supplied (chain of) referrals onto the
288      * end of the current (chain of) referrals.
289      */
290     @Override
appendUnprocessedReferrals(LdapReferralException ex)291     public void appendUnprocessedReferrals(LdapReferralException ex) {
292         if (refEx != null) {
293             refEx = refEx.appendUnprocessedReferrals(ex);
294         } else {
295             refEx = ex.appendUnprocessedReferrals(refEx);
296         }
297     }
298 
setNamingException(NamingException e)299     final void setNamingException(NamingException e) {
300         errEx = e;
301     }
302 
getReferredResults( LdapReferralContext refCtx)303     protected abstract AbstractLdapNamingEnumeration<? extends NameClassPair> getReferredResults(
304             LdapReferralContext refCtx) throws NamingException;
305 
306     /*
307      * Iterate through the URLs of a referral. If successful then perform
308      * a search operation and merge the received results with the current
309      * results.
310      */
hasMoreReferrals()311     protected final boolean hasMoreReferrals() throws NamingException {
312 
313         if ((refEx != null) &&
314             (refEx.hasMoreReferrals() ||
315              refEx.hasMoreReferralExceptions())) {
316 
317             if (homeCtx.handleReferrals == LdapClient.LDAP_REF_THROW) {
318                 throw (NamingException)(refEx.fillInStackTrace());
319             }
320 
321             // process the referrals sequentially
322             while (true) {
323 
324                 LdapReferralContext refCtx =
325                     (LdapReferralContext)refEx.getReferralContext(
326                     homeCtx.envprops, homeCtx.reqCtls);
327 
328                 try {
329 
330                     update(getReferredResults(refCtx));
331                     break;
332 
333                 } catch (LdapReferralException re) {
334 
335                     // record a previous exception
336                     if (errEx == null) {
337                         errEx = re.getNamingException();
338                     }
339                     refEx = re;
340                     continue;
341 
342                 } finally {
343                     // Make sure we close referral context
344                     refCtx.close();
345                 }
346             }
347             return hasMoreImpl();
348 
349         } else {
350             cleanup();
351 
352             if (errEx != null) {
353                 throw errEx;
354             }
355             return (false);
356         }
357     }
358 
359     /*
360      * Merge the entries and/or referrals from the supplied enumeration
361      * with those of the current enumeration.
362      */
update(AbstractLdapNamingEnumeration<? extends NameClassPair> ne)363     protected void update(AbstractLdapNamingEnumeration<? extends NameClassPair> ne) {
364         // Cleanup previous context first
365         homeCtx.decEnumCount();
366 
367         // New enum will have already incremented enum count and recorded clnt
368         homeCtx = ne.homeCtx;
369         enumClnt = ne.enumClnt;
370 
371         // Do this to prevent referral enumeration (ne) from decrementing
372         // enum count because we'll be doing that here from this
373         // enumeration.
374         ne.homeCtx = null;
375 
376         // Record rest of information from new enum
377         posn = ne.posn;
378         limit = ne.limit;
379         res = ne.res;
380         entries = ne.entries;
381         refEx = ne.refEx;
382         listArg = ne.listArg;
383     }
384 
385     @SuppressWarnings("deprecation")
finalize()386     protected final void finalize() {
387         cleanup();
388     }
389 
cleanup()390     protected final void cleanup() {
391         if (cleaned) return; // been there; done that
392 
393         if(enumClnt != null) {
394             enumClnt.clearSearchReply(res, homeCtx.reqCtls);
395         }
396 
397         enumClnt = null;
398         cleaned = true;
399         if (homeCtx != null) {
400             homeCtx.decEnumCount();
401             homeCtx = null;
402         }
403     }
404 
405     @Override
close()406     public final void close() {
407         cleanup();
408     }
409 }
410