001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.runtime.net.ldap;
020
021import java.io.IOException;
022import java.security.Security;
023import java.util.Enumeration;
024import java.util.Hashtable;
025
026import javax.naming.NamingEnumeration;
027import javax.naming.NamingException;
028import javax.naming.directory.Attribute;
029import javax.naming.directory.Attributes;
030import javax.naming.directory.BasicAttribute;
031import javax.naming.directory.BasicAttributes;
032import javax.naming.directory.DirContext;
033import javax.naming.directory.InitialDirContext;
034import javax.naming.directory.ModificationItem;
035import javax.naming.directory.SearchControls;
036import javax.naming.directory.SearchResult;
037import javax.naming.ldap.InitialLdapContext;
038
039import lucee.commons.lang.ClassException;
040import lucee.commons.lang.ClassUtil;
041import lucee.commons.lang.StringUtil;
042import lucee.runtime.exp.PageException;
043import lucee.runtime.op.Caster;
044import lucee.runtime.type.Collection;
045import lucee.runtime.type.KeyImpl;
046import lucee.runtime.type.Query;
047import lucee.runtime.type.QueryImpl;
048import lucee.runtime.type.util.ListUtil;
049
050import com.sun.net.ssl.internal.ssl.Provider;
051
052
053/**
054 * Ldap Client
055 */
056public final class LDAPClient {
057
058        /**
059         * Field <code>SECURE_NONE</code>
060         */
061        public static final short SECURE_NONE=0;
062        /**
063         * Field <code>SECURE_CFSSL_BASIC</code>
064         */
065        public static final short SECURE_CFSSL_BASIC=1;
066        /**
067         * Field <code>SECURE_CFSSL_CLIENT_AUTH</code>
068         */
069        public static final short SECURE_CFSSL_CLIENT_AUTH=2;
070        
071        /**
072         * Field <code>SORT_TYPE_CASE</code>
073         */
074        public static final int SORT_TYPE_CASE = 0;
075        /**
076         * Field <code>SORT_TYPE_NOCASE</code>
077         */
078        public static final int SORT_TYPE_NOCASE = 1;
079
080        /**
081         * Field <code>SORT_DIRECTION_ASC</code>
082         */
083        public static final int SORT_DIRECTION_ASC = 0;
084        /**
085         * Field <code>SORT_DIRECTION_DESC</code>
086         */
087    public static final int SORT_DIRECTION_DESC = 1;
088    
089        Hashtable env=new Hashtable();
090        
091        
092        /**
093         * constructor of the class
094         * @param server
095         * @param port
096         * @param binaryColumns
097         */
098        public LDAPClient(String server, int port,String[] binaryColumns) {
099
100                        env.put("java.naming.factory.initial", "com.sun.jndi.ldap.LdapCtxFactory");
101                        env.put("java.naming.provider.url", "ldap://" + server+":"+port);
102      
103       // rEAD AS bINARY
104            for(int i = 0; i < binaryColumns.length; i++)env.put("java.naming.ldap.attributes.binary", binaryColumns[i]);
105
106       // Referral
107            env.put("java.naming.referral", "ignore");
108        }
109    
110    /**
111     * sets username password for the connection
112     * @param username
113     * @param password
114     */
115    public void setCredential(String username, String password) {
116        if(username != null) {
117            env.put("java.naming.security.principal", username);
118            env.put("java.naming.security.credentials", password);
119        } 
120        else {
121            env.remove("java.naming.security.principal");
122            env.remove("java.naming.security.credentials");
123        }
124    }
125    
126    /**
127     * sets the secure Level
128     * @param secureLevel [SECURE_CFSSL_BASIC, SECURE_CFSSL_CLIENT_AUTH, SECURE_NONE]
129     * @throws ClassNotFoundException 
130     * @throws ClassException 
131     */
132    public void setSecureLevel(short secureLevel) throws ClassException {
133        // Security
134        if(secureLevel==SECURE_CFSSL_BASIC) {
135            env.put("java.naming.security.protocol", "ssl");
136            env.put("java.naming.ldap.factory.socket", "javax.net.ssl.SSLSocketFactory");
137            //Class.orName("com.sun.net.ssl.internal.ssl.Provider");
138            ClassUtil.loadClass("com.sun.net.ssl.internal.ssl.Provider");
139            
140            Security.addProvider(new Provider());
141            
142        } 
143        else if(secureLevel==SECURE_CFSSL_CLIENT_AUTH) {
144            env.put("java.naming.security.protocol", "ssl");
145            env.put("java.naming.security.authentication", "EXTERNAL");
146        }
147        else {
148            env.put("java.naming.security.authentication", "simple");
149            env.remove("java.naming.security.protocol");
150            env.remove("java.naming.ldap.factory.socket");
151        } 
152    }
153    
154    /**
155     * sets thr referral
156     * @param referral
157     */
158    public void setReferral(int referral) {
159        if(referral > 0) {
160            env.put("java.naming.referral", "follow");
161            env.put("java.naming.ldap.referral.limit", Caster.toString(referral));
162        } 
163        else {
164            env.put("java.naming.referral", "ignore");
165            env.remove("java.naming.ldap.referral.limit");
166        }
167    }
168    
169
170
171        /**
172         * adds LDAP entries to LDAP server
173         * @param dn
174         * @param attributes
175         * @param delimiter 
176         * @throws NamingException 
177         * @throws PageException 
178         */
179        public void add(String dn, String attributes, String delimiter, String seperator) throws NamingException, PageException {
180                DirContext ctx = new InitialDirContext(env);
181            ctx.createSubcontext(dn, toAttributes(attributes,delimiter,seperator));
182            ctx.close();
183        }
184        
185    /**
186     * deletes LDAP entries on an LDAP server
187     * @param dn
188     * @throws NamingException
189     */
190    public void delete(String dn)  throws NamingException{
191        DirContext ctx = new InitialDirContext(env);
192        ctx.destroySubcontext(dn);
193        ctx.close();
194    }
195    
196    /**
197     *  modifies distinguished name attribute for LDAP entries on LDAP server
198     * @param dn
199     * @param attributes
200     * @throws NamingException 
201     */
202    public void modifydn(String dn, String attributes)  throws NamingException{
203        DirContext ctx = new InitialDirContext(env);
204            ctx.rename(dn, attributes);
205            ctx.close();
206    }
207    
208    public void modify(String dn, int modifytype, String strAttributes, String delimiter, String separator) throws NamingException, PageException {
209
210            DirContext context = new InitialDirContext(env);
211            String strArrAttributes[] = toStringAttributes(strAttributes,delimiter);
212            
213            int count = 0;
214            for(int i=0; i<strArrAttributes.length; i++) {
215                String[] attributesValues = getAttributesValues(strArrAttributes[i], separator);
216                if(attributesValues == null)count++;
217                else count+=attributesValues.length;
218            }
219            
220            ModificationItem modItems[] = new ModificationItem[count];
221            BasicAttribute basicAttr = null;
222            int k = 0;
223            for(int i = 0; i < strArrAttributes.length; i++) {
224                String attribute = strArrAttributes[i];
225                String type = getAttrValueType(attribute);
226                String values[] = getAttributesValues(attribute,separator);
227                
228                if(modifytype==DirContext.REPLACE_ATTRIBUTE) {
229                    if(values == null) basicAttr = new BasicAttribute(type);
230                    else basicAttr = new BasicAttribute(type, values[0]);
231                    
232                    modItems[k] = new ModificationItem(modifytype, basicAttr);
233                    k++;
234                    if(values != null && values.length > 1) {
235                        for(int j = 1; j < values.length; j++) {
236                            basicAttr = new BasicAttribute(type, values[j]);
237                            modItems[k] = new ModificationItem(DirContext.ADD_ATTRIBUTE, basicAttr);
238                            k++;
239                        }
240                    }
241                } 
242                else {
243                    for(int j = 0; j < values.length; j++) {
244                        if(type != null || modifytype==DirContext.ADD_ATTRIBUTE)
245                             basicAttr = new BasicAttribute(type, values[j]);
246                        else basicAttr = new BasicAttribute(values[j]);
247                        modItems[k] = new ModificationItem(modifytype, basicAttr);
248                        k++;
249                    }
250                }
251            }
252
253            context.modifyAttributes(dn, modItems);
254            context.close();
255
256    }
257    
258    
259    
260    /**
261     * @param dn
262     * @param strAttributes
263     * @param scope
264     * @param startrow
265     * @param maxrows
266     * @param timeout
267     * @param sort
268     * @param sortType
269     * @param sortDirection
270     * @param start
271     * @param separator
272     * @param filter
273     * @return
274     * @throws NamingException
275     * @throws PageException
276     * @throws IOException 
277     */
278    public Query query(String strAttributes,int scope, int startrow, int maxrows, int timeout, 
279            String[] sort, int sortType, int sortDirection, 
280            String start, String separator, String filter) 
281        throws NamingException, PageException, IOException {
282        //strAttributes=strAttributes.trim();
283        boolean attEQAsterix=strAttributes.trim().equals("*");
284        String[] attributes = attEQAsterix?new String[]{"name","value"}:toStringAttributes(strAttributes,",");
285        
286        
287        
288        // Control
289        SearchControls controls = new SearchControls();
290        controls.setReturningObjFlag(true);
291        controls.setSearchScope(scope);
292        if(!attEQAsterix)controls.setReturningAttributes(toStringAttributes(strAttributes,","));
293        if(maxrows>0)controls.setCountLimit(startrow + maxrows + 1);
294        if(timeout>0)controls.setTimeLimit(timeout);
295        
296
297        InitialLdapContext context = new InitialLdapContext(env, null);
298        
299        // Search
300        Query qry=new QueryImpl(attributes,0,"query");
301        try {
302            NamingEnumeration results = context.search(start, filter, controls);
303            
304            // Fill result
305            int row=1;
306            if(!attEQAsterix) {
307                while(results.hasMoreElements()) {
308                    SearchResult resultRow = (SearchResult)results.next();
309                    if(row++<startrow)continue;
310                    
311                    int len=qry.addRow();
312                    NamingEnumeration rowEnum = resultRow.getAttributes().getAll();
313                    String dn = resultRow.getNameInNamespace(); 
314                    qry.setAtEL("dn",len,dn);
315                    while(rowEnum.hasMore()) {
316                        Attribute attr = (Attribute)rowEnum.next();
317                        Collection.Key key = KeyImpl.init(attr.getID());
318                        Enumeration values = attr.getAll();
319                        Object value;
320                        String existing,strValue;
321                        while(values.hasMoreElements()) {
322                            value = values.nextElement();
323                            strValue=Caster.toString(value,null);
324                            existing=Caster.toString(qry.getAt(key, len,null),null);
325                            if(!StringUtil.isEmpty(existing) && !StringUtil.isEmpty(strValue)) {
326                                value = existing + separator + strValue;
327                                }
328                            else if(!StringUtil.isEmpty(existing))
329                                value = existing;
330                            
331                                qry.setAtEL(key,len,value);
332                        }            
333                    }
334                    if(maxrows>0 && len>=maxrows)break;
335                }
336            }
337            else {
338                
339                outer:while(results.hasMoreElements()) {
340                    SearchResult resultRow = (SearchResult)results.next();
341                    if(row++<startrow)continue;
342                    
343                    Attributes attributesRow = resultRow.getAttributes();
344                    NamingEnumeration rowEnum = attributesRow.getIDs();
345                    while(rowEnum.hasMoreElements()) {
346                        int len=qry.addRow();
347                        String name = Caster.toString(rowEnum.next());
348                        Object value=null;
349                        
350                        try {
351                            value=attributesRow.get(name).get();
352                        }catch(Exception e) {}
353                        
354                        qry.setAtEL("name",len,name);
355                        qry.setAtEL("value",len,value);
356                        if(maxrows>0 && len>=maxrows)break outer;
357                    }
358                    qry.setAtEL("name",qry.size(),"dn");
359                }
360            }
361        }
362        finally {
363            context.close();
364        }
365        // Sort
366        if(sort!=null && sort.length>0) {
367            int order = sortDirection==SORT_DIRECTION_ASC ? Query.ORDER_ASC : Query.ORDER_DESC;
368            for(int i=sort.length-1;i>=0;i--) {
369                String item=sort[i];
370                if(item.indexOf(' ')!=-1)item=ListUtil.first(item," ",true);
371                qry.sort(KeyImpl.getInstance(item),order);
372                //keys[i] = new SortKey(item);
373            }
374        }    
375        return qry;
376    }
377
378    private static String[] toStringAttributes(String strAttributes,String delimiter) throws PageException {
379                return ListUtil.toStringArrayTrim(ListUtil.listToArrayRemoveEmpty(strAttributes,delimiter));            
380        }
381        
382        private static Attributes toAttributes(String strAttributes,String delimiter, String separator) throws PageException {
383                String[] arrAttr = toStringAttributes(strAttributes,delimiter);
384                
385                
386                BasicAttributes attributes = new BasicAttributes();
387        for(int i=0; i<arrAttr.length; i++) {
388            String strAttr = arrAttr[i];
389            
390            // Type
391            int eqIndex=strAttr.indexOf('=');
392            Attribute attr = new BasicAttribute((eqIndex != -1)?strAttr.substring(0, eqIndex).trim():null);
393            
394            // Value
395            String strValue = (eqIndex!=-1)?strAttr.substring( eqIndex+ 1):strAttr;
396            String[] arrValue=ListUtil.toStringArray(ListUtil.listToArrayRemoveEmpty(strValue,separator));
397            
398            // Fill
399            for(int y=0; y<arrValue.length; y++) {
400                attr.add(arrValue[y]);
401            }
402            attributes.put(attr);
403        }
404        return attributes;
405                
406        }
407    
408    private String getAttrValueType(String attribute) {
409        int eqIndex=attribute.indexOf("=");
410        if(eqIndex != -1) return attribute.substring(0, eqIndex).trim();
411        return null;
412    }
413    
414    private String[] getAttributesValues(String attribute, String separator) throws PageException {
415        String strValue = attribute.substring(attribute.indexOf("=") + 1);
416        if(strValue.length() == 0) return null;
417        return ListUtil.toStringArray(ListUtil.listToArrayRemoveEmpty(strValue,separator.equals(", ") ? "," : separator));
418    }
419                
420}