View Javadoc

1   /*
2    * Copyright 2000-2001,2004 The Apache Software Foundation.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.apache.jetspeed.services.ldap;
18  
19  import java.util.Enumeration;
20  import java.util.Hashtable;
21  import java.util.Properties;
22  import java.util.StringTokenizer;
23  import java.util.Vector;
24  import javax.naming.AuthenticationException;
25  import javax.naming.CommunicationException;
26  import javax.naming.Context;
27  import javax.naming.Name;
28  import javax.naming.NameNotFoundException;
29  import javax.naming.NameParser;
30  import javax.naming.NamingEnumeration;
31  import javax.naming.NamingException;
32  import javax.naming.ReferralException;
33  import javax.naming.directory.Attribute;
34  import javax.naming.directory.Attributes;
35  import javax.naming.directory.DirContext;
36  import javax.naming.directory.InitialDirContext;
37  import javax.naming.directory.ModificationItem;
38  import javax.naming.directory.SearchControls;
39  import javax.naming.directory.SearchResult;
40  import javax.servlet.ServletConfig;
41  
42  // Turbine classes
43  import org.apache.turbine.services.InitializationException;
44  import org.apache.turbine.services.TurbineBaseService;
45  import org.apache.turbine.services.TurbineServices;
46  import org.apache.turbine.services.resources.ResourceService;
47  
48  // Jetspeed classes
49  import org.apache.jetspeed.services.logging.JetspeedLogFactoryService;
50  import org.apache.jetspeed.services.logging.JetspeedLogger;
51  
52  /***
53   *
54   * @author <a href="mailto:ender@kilicoglu.nom.tr">Ender KILICOGLU</a>
55   * @author <a href="mailto:sami.leino@netorek.fi">Sami Leino</a>
56   *
57   * @version $Id: LDAPService.java,v 1.6 2004/02/23 03:28:31 jford Exp $ 
58   * 
59   */
60  public class LDAPService extends TurbineBaseService
61  {
62      /***
63       * Static initialization of the logger for this class
64       */    
65      private static final JetspeedLogger logger = JetspeedLogFactoryService.getLogger(LDAPService.class.getName());
66      
67      public static String SERVICE_NAME = "ldap";
68      private static final String DEFAULT_ATTR[] = {
69          "objectclass"
70      };
71      public static final int BASE = 0;
72      public static final int ONE = 1;
73      public static final int SUB = 2;
74      public static final int DEFAULT_PORT = 389;
75      public static final int DEFAULT_SSLPORT = 636;
76      public static final int DEFAULT_LIMIT = 0;
77      public static final int DEFAULT_TIMEOUT = 0;
78      public static final int DEFAULT_VERSION = 3;
79      private static String DEFAULT_CTX = "com.sun.jndi.ldap.LdapCtxFactory";
80  
81      private Hashtable connections;
82      private Connector connector;
83      private int limit;
84      private int timeout;
85      private int version;
86      private String host;
87      private int port;
88      private int sslport;
89      private String basedn;
90      private String managerdn;
91      private String password;
92      private String managerlogin;
93      private int batchsize;
94      private String securityAuthentication;
95      private String securityProtocol;
96      private String socketFactory;
97      private String saslclientpckgs;
98      private String jndiprovider;
99      private boolean anonymousBind;
100     private String listFilter;
101     private String attributesList[];
102     private NameParser parser;
103     private boolean showOpAttributes;
104 	private boolean useCachedDirContexts;
105     private Properties env;
106 
107     /***
108      * Main Connection Function
109      *
110      * Make first connection and store it in connections.
111      *
112      * @param url <code>LDAPURL</code> which locate server to connect.
113      * @return boolean true if success else false.         
114      */
115     private boolean mainConnect(LDAPURL url)
116     {
117         setDefaultEnv();
118         String base = url.getBase();
119         env.put("java.naming.provider.url", base);
120         try
121         {
122             DirContext ctx = new InitialDirContext(env);
123             if (useCachedDirContexts)
124             {
125             	connections.put(basedn, ctx);
126             }
127             if(parser == null) parser = ctx.getNameParser("");
128             return true;
129         }
130         catch(NamingException e)
131         {
132             logger.error ("LDAP Service: Failed to connect to " + url.getUrl(), e);
133         }
134         return false;
135     }
136 
137     /***
138      * Connection Function
139      *
140      * tries to connect given <code>LDAPURL</code>.
141      *
142      * @param url <code>LDAPURL</code> which locate server to connect.
143      * @return DirContext connection context object.
144      */
145     public DirContext connect(LDAPURL url)
146     {
147 
148         String base = url.getBase();
149         DirContext ctx = (DirContext)connections.get(base);
150         if(ctx != null)
151         {
152 			// System.out.println("LDAPService: returning cached context.");
153 			// System.out.println("LDAPService: DN is " + url.getDN());
154         	return ctx;
155         }
156 		else
157 		{
158 			// System.out.println("LDAPService: creating new context for base " + base);
159 			// System.out.println("LDAPService: DN is " + url.getDN());
160 		}
161 		
162         setDefaultEnv();
163         env.put("java.naming.provider.url", base);
164         do
165         {
166             try
167             {
168                 ctx = new InitialDirContext(env);
169                 if (useCachedDirContexts) connections.put(base, ctx);
170                 return ctx;
171             }
172             catch(AuthenticationException e)
173             {
174                 logger.error ("LDAP Service: Authentication error: " + base, e);
175                 if(connector == null)
176                     return null;
177                 Properties pr = connector.referralConnection(env, url, anonymousBind);
178                 if(pr != null)
179                 {
180                     env = pr;
181                     continue;
182                 }
183             }
184             catch(CommunicationException e)
185             {
186                 logger.error("LDAP Service: Communication error: " + base, e);
187                 if(connector == null)
188                     return null;
189                 if(connector.connectionFailed(url))
190                 {
191                     resetConnection(url);
192                     continue;
193                 }
194             }
195             catch(NamingException e)
196             {
197                 logger.error("LDAP Service:Failed to connect to " + base, e);
198             }
199             return ctx;
200         } while(true);
201     }
202 
203     /***
204      * Reset Given Connection Function
205      *
206      * tries to connect given <code>LDAPURL</code>.
207      *
208      * @param url <code>LDAPURL</code> which locate server to connect.
209      *
210      */
211     private void resetConnection(LDAPURL url)
212     {
213 		// System.out.println("LDAPService: resetConnection() called.");
214         connections.remove(url.getBase());
215     }
216      /***
217      * Set Default Environment
218      *
219      * Fill properties necessary to connect.
220      *
221      */
222     private void setDefaultEnv()
223     {
224         showOpAttributes = attributesList != null;
225         env.put("java.naming.referral", "ignore");
226         env.put("java.naming.batchsize", String.valueOf(batchsize));
227 
228         if(anonymousBind)
229         {
230             env.remove("java.naming.security.principal");
231             env.remove("java.naming.security.credentials");
232         }
233         else
234         {
235             env.put("java.naming.security.principal", managerdn);
236             env.put("java.naming.security.credentials", password);
237         }
238 
239         env.put("java.naming.security.authentication", securityAuthentication);
240         if(saslclientpckgs  != null)
241 		{
242 		    env.put("javax.security.sasl.client.pkgs", saslclientpckgs);
243 		}
244 		else
245         {
246             env.remove("javax.security.sasl.client.pkgs");
247         }
248 
249         env.put("java.naming.ldap.derefAliases", "never");
250         env.put("java.naming.ldap.deleteRDN", "true" );
251         env.put("java.naming.ldap.version", String.valueOf(version));
252 
253         if( securityProtocol != null)
254         {
255             env.put("java.naming.security.protocol", securityProtocol);
256             if(securityProtocol.equalsIgnoreCase("ssl"))
257             {
258                 env.put("java.naming.ldap.factory.socket", socketFactory );
259 			}
260         }
261         else
262         {
263             env.remove("java.naming.security.protocol");
264             env.remove("java.naming.ldap.factory.socket");
265         }
266 
267 		// env.put("com.sun.jndi.ldap.trace.ber", System.err);
268         env.put("java.naming.factory.initial", (Object)(jndiprovider));
269     }
270 
271     /***
272      * Disconnection Function
273      *
274      * tries to disconnect all connection.
275      *
276      * @return boolean true if success else false.
277      */
278 
279     public boolean disconnect()
280     {
281 		// System.out.println("LDAPService: disconnect() called.");
282         DirContext ctx = null;
283 
284         for(Enumeration enum = connections.elements(); enum.hasMoreElements();)
285 		{
286 		    try
287             {
288                 ctx = (DirContext)enum.nextElement();
289                 ctx.close();
290             }
291             catch(NamingException e)
292             {
293                 logger.error("LDAP Service: Disconnect failed", e);
294             }
295 		}
296 		
297         connections.clear();
298         return true;
299     }
300 
301     public boolean checkAndCloseContext(Context context)
302     {
303 		try
304         {
305             if (!useCachedDirContexts)
306             {
307             	context.close();
308             	// System.out.println("LDAPService: closeContext() called.");
309             }
310             else
311             {
312             	// System.out.println("LDAPService: context left in cache.");
313             }
314 	        return true;
315         }
316         catch(NamingException e)
317         {
318             logger.error("LDAP Service: closeContext() failed", e);
319 	        return false;
320         }
321     }
322 
323 
324     /***
325      * Delete Atrribute Function
326      *
327      * Delete given attribute for given <code>LDAPURL</code>.
328      *
329      * @param url object affected.
330      * @param at Atribute to delete
331      * @return boolean true if success else false.
332      */
333 
334     public boolean deleteAttribute(LDAPURL url, Attribute at)
335     {
336         try
337         {
338             ModificationItem mods[] = new ModificationItem[1];
339             mods[0] = new ModificationItem(3, at);
340             return modifyAttribute(url, mods);
341         }
342         catch(NamingException e)
343         {
344             logger.debug("LDAP Service: Failed to delete '" + at.getID() + "' attribute for " + url.getUrl(), e);
345         }
346         return false;
347     }
348 
349     /***
350      * Add Attribute Function
351      *
352      * add given attribute to given <code>LDAPURL</code>.
353      *
354      * @param url object affected.
355      * @param at Atribute to add
356      * @return boolean true if success else false.
357      */
358     public boolean addAttribute(LDAPURL url, Attribute at)
359     {
360         try
361         {
362             ModificationItem mods[] = new ModificationItem[1];
363             mods[0] = new ModificationItem(1, at);
364             return modifyAttribute(url, mods);
365         }
366         catch(NamingException e)
367         {
368             logger.debug("LDAP Service: Failed to add '" + at.getID() + "' attribute for " + url.getUrl(), e);
369         }
370         return false;
371     }
372 
373     /***
374      * Add entry Function
375      *
376      * tries to add object with given <code>LDAPURL</code> and
377      * with given attributes.
378      *
379      * @param url object to create.
380      * @param at Atributes to add
381      * @return boolean true if success else false.
382      */
383     public boolean addEntry(LDAPURL url, Attributes at)
384     {
385         DirContext ctx = connect(url);
386 
387         if(ctx == null)
388             return false;
389         try
390         {
391             ctx.createSubcontext(url.getDN(), at);
392             checkAndCloseContext(ctx);
393         }
394         catch(ReferralException e)
395         {
396             LDAPURL myurl = getReferralUrl(e);
397             return addEntry(myurl, at);
398         }
399         catch(NamingException e)
400         {
401 
402 e.printStackTrace();
403 
404             logger.error("LDAP Service: Failed to add new entry " + url.getDN(), e);
405             return false;
406         }
407         return true;
408     }
409 
410     /***
411      * Query existense of an Object Function
412      *
413      * tries to locate given <code>LDAPURL</code>.
414      *
415      * @param url object affected.
416      * @return boolean true if exist else false.
417      */
418     public boolean exists(LDAPURL url)
419     {
420         DirContext ctx = connect(url);
421         if(ctx == null) return false;
422 
423         try
424         {
425             NamingEnumeration results = search(ctx, url.getDN(), "(objectclass=*)", DEFAULT_ATTR, 0, false);
426             checkAndCloseContext(ctx);
427             return true;
428         }
429         catch(NameNotFoundException _ex)
430         {
431             return false;
432         }
433         catch(NamingException _ex)
434         {
435             return false;
436         }
437     }
438 
439     /***
440      * Compare Function
441      *
442      * Compare given <code>LDAPURL</code>s.
443      *
444      * @param srcUrl object affected.
445      * @param dstUrl object affected.
446      * @return int 0 same host+DN, 1 same DN,2 child,3 no relation.
447      */
448     public int compare(LDAPURL srcUrl, LDAPURL dstUrl)
449     {
450         if(!srcUrl.sameHosts(dstUrl))
451             return 0;
452         Name src = parse(srcUrl.getDN());
453         Name dst = parse(dstUrl.getDN());
454         if(dst.compareTo(src) == 0)
455             return 1;
456         if(dst.startsWith(src))
457             return 2;
458         Name prefix = src.getPrefix(src.size() - 1);
459         return dst.compareTo(prefix) != 0 ? 0 : 3;
460     }
461 
462     /***
463      * Import Function
464      *
465      * Import given <code>LDAPURL</code> to another dn.
466      *
467      * @param url object to import.
468      * @param dn Dn of new object.
469      * @param entry attributes.
470      * @param type 0 addnew, 1 update, 2 sync.
471      * @return int 1 success, 0 unknown type,-1 failure.
472      */
473     public int importEntry(LDAPURL url, String dn, Attributes entry, int type)
474     {
475         boolean rs = false;
476         LDAPURL myurl = new LDAPURL(url.getHost(), url.getPort(), dn);
477         if(type == 0)
478             rs = addEntry(myurl, entry);
479         else
480 
481         if(type == 1)
482             rs = updateEntry(myurl, entry);
483         else
484         if(type == 2)
485             rs = synchEntry(myurl, entry);
486         else
487             return 0;
488         return !rs ? -1 : 1;
489     }
490 
491     /***
492      * Modify Function
493      *
494      * Modify given <code>LDAPURL</code> with fiven modification items.
495      *
496      * @param url object to modify.
497      * @param mods Modification items.
498      * @exception NamingException
499      * @return boolean true if success else false.
500      */
501     private boolean modifyAttribute(LDAPURL url, ModificationItem mods[])
502         throws NamingException
503     {
504         DirContext ctx = connect(url);
505         if(ctx == null) return false;
506 
507         try
508         {
509             ctx.modifyAttributes(url.getDN(), mods);
510             checkAndCloseContext(ctx);
511         }
512         catch(ReferralException e)
513         {
514             LDAPURL myurl = getReferralUrl(e);
515             return modifyAttribute(myurl, mods);
516         }
517         return true;
518     }
519 
520     /***
521      * Build LDAPURL Function
522      *
523      * Build <code>LDAPURL</code> with given DN.
524      *
525      * @param DN DN value for object.
526      * @return LDAPURL build with given DN.
527      */
528     public LDAPURL buildURL(String DN)
529     {
530       return new LDAPURL(host,port,DN + "," + basedn);
531     }
532 
533     /***
534      * Read Attributes Function
535      *
536      * Return attributes for given <code>LDAPURL</code>.
537      *
538      * @param url object to read attributes.
539      * @return Attributes attributes for given url.
540      */
541     public Attributes read(LDAPURL url)
542     {
543         DirContext ctx = connect(url);
544         if(ctx == null) return null;
545         
546         Attributes attrs = null;
547         try
548         {
549             if(showOpAttributes)
550             {
551                 attrs = ctx.getAttributes(url.getDN(), attributesList);
552             }
553             else
554             {
555                 attrs = ctx.getAttributes(url.getDN());
556             }
557             checkAndCloseContext(ctx);
558         }
559         catch(ReferralException e)
560         {
561             LDAPURL myurl = getReferralUrl(e);
562             if(myurl.getDN().length() == 0)
563             {
564                 myurl.setDN(url.getDN());
565             }
566             return read(myurl);
567         }
568         catch(CommunicationException e)
569         {
570             if(connector == null)
571             {
572                 logger.debug("LDAP Service: Communication error : " + url.getBase(), e);
573                 return null;
574             }
575             if(connector.connectionFailed(url))
576             {
577                 resetConnection(url);
578 			}
579         }
580         catch(NamingException e)
581         {
582             logger.debug("LDAP Service: Failed to read entry " + url.getDN(), e);
583             return null;
584         }
585         return attrs;
586     }
587 
588     /***
589      * Rename Entry Function
590      *
591      * Rename given <code>LDAPURL</code> with given DN.
592      *
593      * @param url object to modify.
594      * @param newDN DN value for new object.
595      * @return boolean true if success else false.
596      */
597     public boolean renameEntry(LDAPURL url, String newDN)
598     {
599         DirContext ctx = connect(url);
600         if(ctx == null) return false;
601 
602         try
603         {
604             ctx.rename(url.getDN(), newDN);
605             checkAndCloseContext(ctx);
606         }
607         catch(ReferralException e)
608         {
609             logger.debug("LDAP Service: Failed to rename entry. (not supported for referrals)", e);
610             return false;
611         }
612         catch(NamingException e)
613         {
614             logger.debug("LDAP Service: Failed to rename entry " + url.getDN(), e);
615             return false;
616         }
617         return true;
618     }
619 
620     /***
621      * Sync Entry Function
622      *
623      * Sync given <code>LDAPURL</code> with given atrributes.
624      *
625      * @param url object to sync.
626      * @param ats Modification items.
627      * @return boolean true if success else false.
628      */
629     public boolean synchEntry(LDAPURL url, Attributes ats)
630     {
631         DirContext ctx = connect(url);
632         if(ctx == null) return false;
633 
634         try
635         {
636             ctx.modifyAttributes(url.getDN(), 2, ats);
637             checkAndCloseContext(ctx);
638         }
639         catch(ReferralException e)
640         {
641             LDAPURL myurl = getReferralUrl(e);
642             return synchEntry(url, ats);
643         }
644         catch(NameNotFoundException _ex)
645         {
646             try
647             {
648                 ctx.createSubcontext(url.getDN(), ats);
649             }
650             catch(NamingException _ex2)
651             {
652                 return false;
653             }
654         }
655         catch(NamingException e)
656         {
657             logger.debug("LDAP Service: Failed to synchronize entries", e);
658             return false;
659         }
660         return true;
661     }
662 
663     /***
664      * Delete Attributes Function
665      *
666      * Delete Attributes for given <code>LDAPURL</code>.
667      *
668      * @param url object to modify.
669      * @param ats Attributes to delete.
670      * @return boolean true if success else false.
671      */
672     public boolean deleteAttrs(LDAPURL url, Attributes ats)
673     {
674         DirContext ctx = connect(url);
675         if(ctx == null) return false;
676 
677         try
678         {
679             ctx.modifyAttributes(url.getDN(), DirContext.REMOVE_ATTRIBUTE, ats);
680             checkAndCloseContext(ctx);
681         }
682         catch(ReferralException e)
683         {
684             LDAPURL myurl = getReferralUrl(e);
685             return synchEntry(url, ats);
686         }
687         catch(NameNotFoundException _ex)
688         {
689             try
690             {
691                 ctx.createSubcontext(url.getDN(), ats);
692 	            checkAndCloseContext(ctx);
693             }
694             catch(NamingException _ex2)
695             {
696                 return false;
697             }
698         }
699         catch(NamingException e)
700         {
701             logger.debug("LDAP Service: Failed to delete Attributes", e);
702             return false;
703         }
704         return true;
705     }
706 
707     /***
708      * Delete Entry Function
709      *
710      * Delete given <code>LDAPURL</code>.
711      *
712      * @param url object to delete.
713      * @return boolean true if success else false.
714      */
715     public boolean deleteEntry(LDAPURL url)
716     {
717         DirContext ctx = connect(url);
718         if(ctx == null) return false;
719 
720         try
721         {
722             ctx.destroySubcontext(url.getDN());
723             checkAndCloseContext(ctx);
724         }
725         catch(ReferralException e)
726         {
727             LDAPURL myurl = getReferralUrl(e);
728             return deleteEntry(myurl);
729         }
730         catch(NamingException e)
731         {
732             logger.debug("LDAP Service: Failed to delete entry " + url.getDN(), e);
733             return false;
734         }
735         return true;
736     }
737 
738     /***
739      * Find Entry Name Function
740      *
741      * Return entry name for given <code>LDAPURL</code>.
742      *
743      * @param url object to modify.
744      * @return LDAPURL real entry DN.
745      */
746     public LDAPURL findEntryName(LDAPURL url)
747     {
748         DirContext ctx = connect(url);
749         if(ctx == null) return null;
750         
751         Name name = parse(url.getDN());
752         String base = name.getPrefix(name.size() - 1).toString();
753         String dn = url.getDN();
754         String rdn = name.get(name.size() - 1).toString();
755         int i = 1;
756         boolean foundName = true;
757 
758         while(foundName)
759         {
760             try
761             {
762                 NamingEnumeration results = search(ctx, dn, "(objectclass=*)", DEFAULT_ATTR, 0, false);
763                 if(i == 1)
764                     rdn = rdn + " copy";
765                 else
766                 if(i == 2)
767                     rdn = rdn + " " + i;
768                 else
769                 if(i >= 3)
770                     rdn = rdn.substring(0, rdn.length() - 1) + i;
771                 dn = rdn + ", " + base;
772                 i++;
773             }
774             catch(NameNotFoundException _ex)
775             {
776                 foundName = false;
777                 return new LDAPURL(url.getHost(), url.getPort(), dn);
778             }
779             catch(NamingException _ex)
780             {
781                 return null;
782             }
783         }
784         
785         checkAndCloseContext(ctx);
786 
787         return null;
788     }
789     
790     /***
791      * Delete Tree Function
792      *
793      * Delete record with all child node <code>LDAPURL</code>.
794      *
795      * @param url object to modify.
796      * @return boolean true if success else false.
797      */
798     public boolean deleteTree(LDAPURL url)
799     {
800         DirContext ctx = connect(url);
801         if(ctx == null) return false;
802 
803         String entryDN = null;
804         LDAPURL myurl = null;
805         String baseDN = url.getDN();
806 
807         try
808         {
809             for(NamingEnumeration results = search(ctx, baseDN, "(objectclass=*)", DEFAULT_ATTR, 1, false); results.hasMore();)
810             {
811                 SearchResult si = (SearchResult)results.next();
812                 entryDN = getFixedDN(si.getName(), baseDN);
813                 myurl = new LDAPURL(url.getHost(), url.getPort(), entryDN);
814                 if(!deleteTree(myurl))
815                 {
816                     return false;
817                 }
818             }
819 
820 			checkAndCloseContext(ctx);
821         }
822         catch(NamingException e)
823         {
824             logger.debug("LDAP Service: Delete tree failed", e);
825             return false;
826         }
827         return deleteEntry(url);
828     }
829 
830     /***
831      * Transfer Function
832      *
833      * Transfer given <code>LDAPURL</code> to other <code>LDAPURL</code>.
834      *
835      * @param fromUrl object to transfer.
836      * @param toUrl target object.
837      * @param delete delete after transfer.
838      * @param replace replace if exist.
839      * @param withChildren transfer with childs.
840      * @return boolean true if success else false.
841      */
842     public boolean transfer(LDAPURL fromUrl, LDAPURL toUrl, boolean delete, boolean replace, boolean withChildren)
843     {
844         LDAPURL dstUrl = toUrl;
845         int rc = compare(fromUrl, toUrl);
846         if(rc == 1)
847             dstUrl = findEntryName(dstUrl);
848         if(withChildren)
849             return transferTreeSub(fromUrl, dstUrl, delete, replace);
850         else
851             return transferEntry(fromUrl, dstUrl, delete, replace);
852 
853     }
854 
855     /***
856      * Transfer with updates Function
857      *
858      * Transfer updated <code>LDAPURL</code> with given modification items
859      * to other <code>LDAPURL</code>.
860      *
861      * @param fromUrl object to transfer.
862      * @param toUrl target object.
863      * @param delete delete after transfer.
864      * @param replace replace if exist.
865      * @param ats attributes to update.
866      * @return boolean true if success else false.
867      */
868     public boolean transferEntry(LDAPURL fromUrl, Attributes ats, LDAPURL toUrl, boolean delete, boolean replace)
869     {
870         if(delete && !deleteEntry(fromUrl))
871             return false;
872         if(updateEntry(toUrl, ats, replace))
873             return true;
874         if(delete)
875             addEntry(fromUrl, ats);
876         return false;
877     }
878 
879     /***
880      * Transfer without updates Function
881      *
882      * Transfer <code>LDAPURL</code> to other <code>LDAPURL</code>.
883      *
884      * @param fromUrl object to transfer.
885      * @param toUrl target object.
886      * @param delete delete after transfer.
887      * @param replace replace if exist.
888      * @return boolean true if success else false.
889      */
890 
891     public boolean transferEntry(LDAPURL fromUrl, LDAPURL toUrl, boolean delete, boolean replace)
892     {
893         Attributes ats = read(fromUrl);
894         if(ats == null)
895             return false;
896         else
897             return transferEntry(fromUrl, ats, toUrl, delete, replace);
898     }
899 
900     /***
901      * Transfer Tree Function
902      *
903      * Transfer <code>LDAPURL</code> with all child to other <code>LDAPURL</code>.
904      *
905      * @param fromUrl object to transfer.
906      * @param toUrl target object.
907      * @param delete delete after transfer.
908      * @param replace replace if exist.
909      * @return boolean true if success else false.
910      */
911     private boolean transferTreeSub(LDAPURL fromUrl, LDAPURL toUrl, boolean delete, boolean replace)
912     {
913         DirContext ctx = connect(fromUrl);
914         if(ctx == null) return false;
915 
916         Attributes ats = read(fromUrl);
917         if(ats == null) return false;
918         
919         String srcDN = fromUrl.getDN();
920         String dstDN = toUrl.getDN();
921         boolean createdBase = false;
922         boolean rc = false;
923         boolean moreReferrals = true;
924 
925         while(moreReferrals)
926         {
927             try
928             {
929                 NamingEnumeration results = search(ctx, srcDN, "(objectclass=*)", DEFAULT_ATTR, 1, false);
930                 if(!results.hasMore())
931                 {
932                     if(!transferEntry(fromUrl, ats, toUrl, delete, replace))
933                         return false;
934                 } else
935                 {
936                     String name = null;
937                     if(!createdBase)
938                     {
939                         if(!updateEntry(toUrl, ats, replace))
940                             return false;
941                         createdBase = true;
942                     }
943                     LDAPURL srcUrl;
944                     LDAPURL dstUrl;
945                     for(; results.hasMore(); transferTreeSub(srcUrl, dstUrl, delete, replace))
946                     {
947                         SearchResult si = (SearchResult)results.next();
948                         name = fixName(si.getName());
949                         String tmpSrcDN = getDN(name, srcDN);
950                         srcUrl = new LDAPURL(fromUrl.getHost(), fromUrl.getPort(), tmpSrcDN);
951                         String tmpDstDN = getDN(name, dstDN);
952                         dstUrl = new LDAPURL(toUrl.getHost(), toUrl.getPort(), tmpDstDN);
953                     }
954 
955                     if(delete && !deleteEntry(fromUrl))
956                         return false;
957                 }
958                 moreReferrals = false;
959             }
960             catch(ReferralException e)
961             {
962                 if(delete)
963                 {
964                     moreReferrals = false;
965                 }
966                	else
967                 {
968                     if(!createdBase)
969                     {
970                         if(!updateEntry(toUrl, ats, replace)) return false;
971                         createdBase = true;
972                     }
973 
974                     LDAPURL srcUrl = getReferralUrl(e);
975                     String tmpDstDN = getName(srcUrl.getDN()) + ", " + dstDN;
976                     LDAPURL dstUrl = new LDAPURL(toUrl.getHost(), toUrl.getPort(), tmpDstDN);
977                     boolean rs = transferTreeSub(srcUrl, dstUrl, delete, replace);
978                     if(!rs)return false;
979 
980                     moreReferrals = e.skipReferral();
981                     try
982                     {
983                     	// Close old context
984                     	checkAndCloseContext(ctx);
985                         ctx = (DirContext)e.getReferralContext();
986                     }
987                     catch(NamingException _ex) { }
988                 }
989             }
990             catch(NamingException e)
991             {
992                 logger.debug("LDAP Service: Transfer Tree failed", e);
993                 return false;
994             }
995         }
996 
997         checkAndCloseContext(ctx);
998         return true;
999     }
1000 
1001     /***
1002      * Update Atribute Function
1003      *
1004      * Update an attribute for given <code>LDAPURL</code>.
1005      *
1006      * @param url object to update.
1007      * @param at atrribute to update.
1008      * @return boolean true if success else false.
1009      */
1010     public boolean updateAttribute(LDAPURL url, Attribute at)
1011     {
1012         try
1013         {
1014             ModificationItem mods[] = new ModificationItem[1];
1015             mods[0] = new ModificationItem(2, at);
1016             return modifyAttribute(url, mods);
1017         }
1018         catch(NamingException e)
1019         {
1020             logger.debug("LDAP Service: Failed to update '" + at.getID() + "' attribute for " + url.getUrl(), e);
1021         }
1022         return false;
1023     }
1024 
1025     /***
1026      * Update Atributes Function
1027      *
1028      * Update attributes for given <code>LDAPURL</code>.
1029      *
1030      * @param url object to update.
1031      * @param at atrributes to update.
1032      * @return boolean true if success else false.
1033      */
1034     public boolean updateEntry(LDAPURL url, Attributes at)
1035     {
1036         DirContext ctx = connect(url);
1037         if(ctx == null) return false;
1038 
1039         try
1040         {
1041             ctx.modifyAttributes(url.getDN(), 2, at);
1042 			checkAndCloseContext(ctx);
1043         }
1044         catch(ReferralException e)
1045         {
1046             LDAPURL myurl = getReferralUrl(e);
1047             return updateEntry(myurl, at);
1048         }
1049         catch(NamingException e)
1050         {
1051             logger.error("LDAP Service: Failed to update entry " + url.getDN(), e);
1052             return false;
1053         }
1054         return true;
1055     }
1056  
1057     /***
1058      * Update Entry Function
1059      *
1060      * Update attributes for given <code>LDAPURL</code>.
1061      *
1062      * @param url object to update.
1063      * @param ats atrributes to update.
1064      * @param replace replace if exist.
1065      * @return boolean true if success else false.
1066      */
1067     public boolean updateEntry(LDAPURL url, Attributes ats, boolean replace)
1068     {
1069         return replace ? synchEntry(url, ats) : addEntry(url, ats);
1070     }
1071 
1072     /***
1073      * Search Function
1074      *
1075      * Search objects for given Base DN and filter.
1076      *
1077      * @param ctx directory context.
1078      * @param dn Base search DN.
1079      * @param filter Search filter.
1080      * @param attribs attributes to receive.
1081      * @param type search scope 1 Subscope, else 0.
1082      * @exception NamingException
1083      * @return NamingEnumeration Results.
1084      */
1085     public NamingEnumeration search(DirContext ctx, String dn, String filter, String attribs[], int type)
1086         throws NamingException
1087     {
1088 		return search(ctx, dn, filter, attribs, type, true);
1089     }
1090 
1091     /***
1092      * Search Function
1093      *
1094      * Search objects for given Base DN and filter.
1095      *
1096      * @param ctx directory context.
1097      * @param dn Base search DN.
1098      * @param filter Search filter.
1099      * @param attribs attributes to receive.
1100      * @param type search scope 2 Subscope, else 1.
1101      * @param setLimits enable limits.
1102      * @exception NamingException
1103      * @return NamingEnumeration Results.
1104      */
1105     private NamingEnumeration search(DirContext ctx, String dn, String filter, String attribs[], int type, boolean setLimits)
1106         throws NamingException
1107     {
1108         SearchControls constraints = new SearchControls();
1109         constraints.setSearchScope(type);
1110         constraints.setReturningAttributes(attribs);
1111         if(setLimits)
1112         {
1113             constraints.setCountLimit(limit);
1114             constraints.setTimeLimit(timeout);
1115         }
1116         NamingEnumeration results = ctx.search(dn, filter, constraints);
1117         return results;
1118     }
1119 
1120     /***
1121      * Search Function
1122      *
1123      * Search objects for given BaseURL and filter.
1124      *
1125      * @param url Base URL .
1126      * @param filter Search filter.
1127      * @param attribs attributes to receive.
1128      * @param subTreeScope true subtree else false.
1129      * @return Vector Results.
1130      */
1131     public Vector search(LDAPURL url, String filter, String attribs[], boolean subTreeScope)
1132     {
1133     	/*
1134 		System.out.println("===== LDAPService: search");
1135 		System.out.println("===== LDAPService: " + url);
1136 		System.out.println("===== LDAPService: " + filter);
1137 		System.out.println("===== LDAPService: " + attribs);
1138 		System.out.println("===== LDAPService: " + subTreeScope);
1139 		*/
1140 		
1141         Vector results = new Vector();
1142         String attrs[] = new String[attribs.length + 1];
1143         attrs[0] = "objectclass";
1144         System.arraycopy(attribs, 0, attrs, 1, attribs.length);
1145         int scope = subTreeScope ? 2 : 1;
1146         subSearch(url, filter, attrs, scope, results);
1147 
1148         return results;
1149     }
1150 
1151     /***
1152      * Search Function
1153      *
1154      * Search objects for given BaseURL and filter.
1155      *
1156      * @param url Base URL .
1157      * @param filter Search filter.
1158      * @param attribs attributes to receive.
1159      * @param scope true subtree else false.
1160      * @param rs Result
1161      * @return boolean true if success else false.
1162      */
1163     private boolean subSearch(LDAPURL url, String filter, String attribs[], int scope, Vector rs)
1164     {
1165         DirContext ctx = connect(url);
1166         if(ctx == null) return false;
1167         
1168         String entryDN = null;
1169         Attributes at = null;
1170         Attribute a = null;
1171         LDAPURL myurl = null;
1172         int subscope = 0;
1173         String baseDN = url.getDN();
1174 
1175         boolean moreReferrals = true;
1176         while(moreReferrals)
1177 		{
1178 		    try
1179             {
1180                 Vector vl;
1181                 for(NamingEnumeration results = search(ctx, baseDN, filter, attribs, scope); results.hasMore(); rs.addElement(vl))
1182                 {
1183                     SearchResult si = (SearchResult)results.next();
1184                     vl = new Vector(attribs.length);
1185                     entryDN = getFixedDN(si.getName(), baseDN);
1186                     myurl = new LDAPURL(url.getHost(), url.getPort(), entryDN);
1187                     vl.addElement(myurl);
1188                     at = si.getAttributes();
1189                     for(int i = 1; i < attribs.length; i++)
1190                     {
1191                         a = at.get(attribs[i]);
1192                         if(a == null)
1193                         {
1194                             vl.addElement("N/A");
1195                         } else
1196                         {
1197                             Object v = a.get();
1198                             if(v instanceof byte[])
1199                                 vl.addElement(v);
1200                             else
1201                                 vl.addElement(a.get().toString());
1202                         }
1203                     }
1204                 }
1205                 moreReferrals = false;
1206             }
1207 
1208             catch(ReferralException e)
1209             {
1210                 myurl = getReferralUrl(e);
1211                 subscope = scope != 1 ? scope : 0;
1212                 boolean error = subSearch(myurl, filter, attribs, subscope, rs);
1213                 if(!error) return error;
1214                 
1215                 moreReferrals = e.skipReferral();
1216                 try
1217                 {
1218                    	// Close old context
1219                    	checkAndCloseContext(ctx);
1220                     ctx = (DirContext)e.getReferralContext();
1221                 }
1222                 catch(NamingException _ex) { }
1223             }
1224             catch(NamingException e)
1225             {
1226                 logger.debug("LDAP Service: Search failed", e);
1227                 return false;
1228             }
1229         }
1230         
1231        	checkAndCloseContext(ctx);
1232         return true;
1233     }
1234 
1235     /***
1236      * Get value Function
1237      *
1238      * Return value for attribute value pair.
1239      *
1240      * @param attrvalue input.
1241      * @return String Value.
1242      */
1243     public String removeAttrName(String attrvalue)
1244     {
1245         StringTokenizer token = new StringTokenizer(attrvalue,"=");
1246         if (token.countTokens()==2)
1247         {
1248         	token.nextToken();
1249         	return token.nextToken();
1250         }
1251         else
1252         {
1253             return attrvalue;
1254         }
1255     }
1256  
1257     /***
1258      * Return full DN Function
1259      *
1260      * Add Base DN to given DN.
1261      *
1262      * @param rdn full DN.
1263      * @param base Base DN.
1264      * @return String DN.
1265      */
1266     private String getFixedDN(String rdn, String base)
1267     {
1268         return getDN(fixName(rdn), base);
1269     }
1270 
1271     /***
1272      * Return Name Function
1273      *
1274      * Return name for given DN.
1275      *
1276      * @param dn DN.
1277      * @return String Name.
1278      */
1279     public String getName(String dn)
1280     {
1281         try
1282         {
1283             Name nm = parser.parse(dn);
1284             return nm.get(nm.size() - 1).toString();
1285         }
1286         catch(NamingException _ex)
1287         {
1288             return null;
1289         }
1290     }
1291 
1292     /***
1293      * Fix Name Function
1294      *
1295      * Fix chars .
1296      *
1297      * @param name Name to fix.
1298      * @return String Fixed name.
1299      */
1300     private String fixName(String name)
1301     {
1302         if(name.length() > 0 && name.charAt(0) == '"')
1303         {
1304             int size = name.length() - 1;
1305             StringBuffer buf = new StringBuffer();
1306             for(int i = 1; i < size; i++)
1307             {
1308                 if(name.charAt(i) == '/')
1309                     buf.append("//");
1310                 buf.append(name.charAt(i));
1311             }
1312 
1313             return buf.toString();
1314         }
1315         else
1316         {
1317             return name;
1318         }
1319     }
1320 
1321     /***
1322      * Return full DN Function
1323      *
1324      * Add Base DN to given DN.
1325      *
1326      * @param rdn DN.
1327      * @param base Base DN.
1328      * @return String full DN.
1329      */
1330     private String getDN(String rdn, String base)
1331     {
1332         if(rdn.length() == 0)
1333             return base;
1334         if(base.length() == 0)
1335             return rdn;
1336         else
1337             return rdn + ", " + base;
1338     }
1339 
1340     /***
1341      * Return Name Function
1342      *
1343      * Add Base DN to given DN.
1344      *
1345      * @param dn full DN.
1346      * @return Name Name for given DN.
1347      */
1348     public Name parse(String dn)
1349     {
1350         try
1351         {
1352             return parser.parse(dn);
1353         }
1354         catch(NamingException _ex)
1355         {
1356             return null;
1357         }
1358     }
1359 
1360     /***
1361      * Get Referral URL Function
1362      *
1363      * Return <code>LDAPURL</code> extracted from exception.
1364      *
1365      * @param e Exception to extract.
1366      * @return LDAPURL referrral URL.
1367      */
1368     public LDAPURL getReferralUrl(ReferralException e)
1369     {
1370         String url = (String)e.getReferralInfo();
1371         try
1372         {
1373             return new LDAPURL(url);
1374         }
1375         catch(Exception ex)
1376         {
1377             logger.debug("Invalid url: " + ex.getMessage() + " " + url);
1378         }
1379         return null;
1380     }
1381 
1382     ///////////////////////////////////////////////////////////////////////////
1383     // Service Init
1384     ///////////////////////////////////////////////////////////////////////////
1385 
1386     /***
1387      * This is the early initialization method called by the
1388      * Turbine <code>Service</code> framework
1389      * @param conf The <code>ServletConfig</code>
1390      * @exception InitializationException if the service fails to initialize
1391      */
1392     public void init( ServletConfig conf ) throws InitializationException
1393     {
1394         connections = new Hashtable();
1395         connector = null;
1396         parser = null;
1397         env = new Properties();
1398         ResourceService serviceConf = ((TurbineServices)TurbineServices.getInstance())
1399                                                      .getResources(SERVICE_NAME);
1400         this.host = serviceConf.getString("host");
1401         this.port = serviceConf.getInt("port",DEFAULT_PORT);
1402         this.sslport = serviceConf.getInt("sslport",DEFAULT_SSLPORT);
1403         this.limit = serviceConf.getInt("limit",DEFAULT_LIMIT);
1404         this.timeout = serviceConf.getInt("timeout",DEFAULT_TIMEOUT);
1405         this.version = serviceConf.getInt("version",DEFAULT_VERSION);
1406         this.listFilter = repair(serviceConf.getString("listfilter","(objectclass=*)"));
1407         this.basedn = repair(serviceConf.getString("basedn"));
1408         this.managerdn = repair(serviceConf.getString("managerdn"));
1409         this.password = serviceConf.getString("password");
1410         this.attributesList = getList(serviceConf.getString("attributeslist")," ");
1411         this.showOpAttributes = serviceConf.getBoolean("showopattributes",false);
1412         this.anonymousBind = serviceConf.getBoolean("anonymousbind",false);
1413         this.securityAuthentication = serviceConf.getString("securityauthentication","simple");
1414         this.securityProtocol = serviceConf.getString("securityprotocol");
1415         this.socketFactory = serviceConf.getString("socketfactory");
1416         this.useCachedDirContexts = serviceConf.getBoolean("contextcache", false);
1417 
1418         this.jndiprovider = serviceConf.getString("jndiprovider",DEFAULT_CTX);
1419         this.saslclientpckgs = serviceConf.getString("saslclientpckgs");
1420         mainConnect(new LDAPURL(host,port,basedn));
1421         setInit(true);
1422     }
1423 
1424     /***
1425      * This is the late initialization method called by the
1426      * Turbine <code>Service</code> framework
1427      * @param conf The <code>ServletConfig</code>
1428      * @exception InitializationException if the service fails to initialize
1429      */
1430     public void init() throws InitializationException
1431     {
1432         while( !getInit() )
1433         {
1434             //Not yet...
1435             try
1436             {
1437                 Thread.sleep( 500 );
1438             }
1439             catch (InterruptedException ie )
1440             {
1441                 logger.error( ie );
1442             }
1443         }
1444     }
1445 
1446     /***
1447      * Repair Given Parameter Function
1448      *
1449      * Repair String read from config.
1450      *
1451      * @param value String to repair.
1452      * @return String Repaired String.
1453      */
1454     private String repair(String value)
1455     {
1456         value = value.replace('/', '=');
1457         value = value.replace('%', ',');
1458         return value;
1459     }
1460 
1461     /***
1462      * Tokenizer Wrapper Function
1463      *
1464      * Tokenize given string with given parameter.
1465      *
1466      * @param value String to repair.
1467      * @param separator separator
1468      * @return String Result.
1469      */
1470     private String[] getList(String value, String separator)
1471     {
1472         if(value == null) return null;
1473 
1474         StringTokenizer tokens = new StringTokenizer(value, separator);
1475         String at[] = new String[tokens.countTokens()];
1476 
1477         for(int i = 0; tokens.hasMoreTokens(); i++)
1478 		{
1479             at[i] = tokens.nextToken();
1480         }
1481 
1482         return at;
1483     }
1484 
1485 }