001    package org.esupportail.cas.server.handlers.ldap;
002    
003    import java.util.Hashtable;
004    
005    import javax.naming.Context;
006    import javax.naming.AuthenticationException;
007    import javax.naming.NamingException;
008    import javax.naming.directory.DirContext;
009    import javax.naming.directory.InitialDirContext;
010    
011    import org.dom4j.Element;
012    import org.esupportail.cas.server.util.log.Log;
013    import org.esupportail.cas.server.util.RedundantHandler;
014    import org.esupportail.cas.server.util.Server;
015    
016    /**
017     * This abstract class implements an LDAP server class, inherited by
018     * BindLdapServer and FastBindLdapServer. 
019     *
020     * @author Pascal Aubry <pascal.aubry at univ-rennes1.fr>
021     * @author Jean-Baptiste Daniel <danielj at sourceforge.net>
022     * @author Patrik Schnellmann - SWITCH
023     */
024    public abstract class LdapServer extends Server {
025    
026            /**
027             * true for a secure access to the LDAP directory, false otherwise.
028             */
029            private boolean secured;
030            
031            /**
032             * the URL of the LDAP directory.
033             */
034            private String url;
035    
036            /**
037             * skip Referrals, useful when using Active Directory
038             */
039            private boolean skipReferrals = false;
040            
041            /**
042             * Constructor.
043             *
044             * @param handlerDebug debugging mode of the handler
045             * @param handler      the handler the server will be used by
046             * @param serverElement the XML element that declares the server 
047             * @throws Exception Exception
048             */
049            public LdapServer(
050                            final Boolean handlerDebug,
051                            final RedundantHandler handler,
052                            final Element serverElement) throws Exception {
053                    super(handlerDebug, handler, serverElement);
054                    traceBegin();
055    
056                    url = getServerSubElementContent(serverElement, "url", true/*needed*/);
057                    trace("url = " + url);
058    
059                    secured = url.substring(0, 5).equals("ldaps");
060                    trace("secured = " + secured);
061    
062                    skipReferrals = ("1".equals(getServerSubElementContent(serverElement,
063                                                                            "skip_referrals", 
064                                                                            false /*not needed*/)
065                                    ));
066                    trace("skip_referrals = " + skipReferrals);
067    
068                    traceEnd();
069            }
070            
071            /**
072             * A String array used to store input tokens (to be replaced). 
073             */
074            private static final String[] INPUT_TOKENS = { 
075                            "%u", "%U", "%d", "%1", "%2", "%3", "%4", "%5", "%6", "%7", "%8", "%9" 
076            };
077            
078            /**
079             * Take a filter (from CASGenericHandler configuration) and a
080             * username, and replace tokens in the filter by their equivalents 
081             * in the username. The rules to replace tokens are the following ones:
082             * - %%: %
083             * - %u: user
084             * - %U: user portion of %u (%U = test when %u = test@domain.tld)
085             * - %d: domain portion of %u (%d = domain.tld when %u = test@domain.tld)
086             * - %1-9: domain tokens (%1 = tld, %2 = domain when %d = domain.tld)
087             *
088             * @param username a username (such as test@domain.com)
089             * @param filter a filter string containing tokens to be replaced
090             *
091             * @return a string corresponding to the input filter, where the
092             * predefined tokens have been replaced by their equivalents.
093             */
094            protected final String replaceTokens(final String filter,
095                            final String username) {
096                    traceBegin();
097                    
098                    /* A String array used to store output tokens (to replace
099                     * input tokens in the replaceTokens() method). */
100                    String[] outputTokens = { 
101                                    "", "", "", "", "", "", "", "", "", "", "", "", "" 
102                    };
103                    
104                    /* -------------------------------------------------------------
105                     * Analyze the username given by the user and deduce the output
106                     * tokens (which will replace the input tokens when calling 
107                     * replaceTokens()).
108                     * ------------------------------------------------------------- */
109                    
110                    // the complete username corresponds to '%u', store it
111                    outputTokens[0] = username;
112                    
113                    // at first cut the username into two parts, seperated by a '@'
114                    String[] userDomain = username.split("@");
115                    // the first cell of userDomain corresponds to '%U', store it
116                    outputTokens[1] = userDomain[0];
117                    
118                    // if a '@' it present,
119                    if (userDomain.length > 1) {
120                            
121                            //the second cell corresponds to '%d', store it
122                            outputTokens[2] = userDomain[1];
123                            
124                            // now look at the '%n' tokens. Split the domain name into pieces
125                            String[] dcArray = userDomain[1].split("\\.");
126                            
127                            // iterate on the array to fill the end of _outputTokens
128                            for (int i = 0; i < dcArray.length; i++) {
129                                    outputTokens[i + 3] = dcArray[dcArray.length - 1 - i];
130                            }
131                    }
132                    
133                    /* -------------------------------------------------------------
134                     * Replace the input tokens by their corresponding parts.
135                     * ------------------------------------------------------------- */
136                    StringBuffer result = new StringBuffer("");
137                    
138                    // at first split _username into parts separated by "%%"
139                    String[] parts = filter.split("%%");
140                    
141                    // for each part, replace the tokens and concatenate to result
142                    for (int i = 0; i < parts.length; i++) {
143                            if (i != 0) {
144                                    result.append("%");
145                            }
146                            for (int j = 0; j < INPUT_TOKENS.length; j++) {
147                                    parts[i] = parts[i].replaceAll(INPUT_TOKENS[j], outputTokens[j]);
148                            }
149                            result.append(parts[i]);
150                    }
151                    traceEnd(result.toString());
152                    return result.toString();
153            }
154            
155            /**
156             * Connect to the LDAP server using specified username and password.
157             *
158             * @param bindDn       the DN to use for the connection
159             * @param bindPassword the associated password
160             *
161             * @return a Connection object on success, null on error. When the function 
162             * returns false, the error code can be retrieved with the connectError() 
163             * method.
164             */     
165            protected final DirContext connect(final String bindDn,
166                                                                               final String bindPassword) {
167                    DirContext connection = null;
168                    traceBegin();
169                    
170                    try {
171                            Hashtable hashtable = new Hashtable(5, 0.75f);
172                            hashtable.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
173                            hashtable.put(Context.PROVIDER_URL, url);
174                            hashtable.put(Context.SECURITY_AUTHENTICATION, "simple");
175                            hashtable.put(Context.SECURITY_PRINCIPAL, bindDn);
176                            hashtable.put(Context.SECURITY_CREDENTIALS, bindPassword.getBytes());
177                            if (secured) {
178                                    hashtable.put(Context.SECURITY_PROTOCOL, "ssl");
179                            }
180                            if (skipReferrals) {
181                                    hashtable.put(Context.REFERRAL, "throw");
182                            }
183                            trace("Connecting to the LDAP directory (url=`" + url + "', username=`" + bindDn + "')...");
184                            connection = new InitialDirContext(hashtable);
185                            setConnectError(CONNECT_SUCCESS);
186                            trace("Connection succeeded.");
187                            traceEnd("ok");
188                            return connection;
189                    } catch (AuthenticationException e) {
190                            trace("Connection failed: " + e.getMessage());
191                            setConnectError(CONNECT_NOAUTH);
192                    } catch (NamingException e) {
193                            Log.warn("Could not connect to \"" + url + "\"");
194                            trace("Connection failed: '" + e.getMessage() + "')");
195                            setConnectError(CONNECT_FAILURE);
196                    }
197    
198                    // connection failed, but try to close the connection however
199                    if (connection != null) {
200                            try {
201                                    trace("Closing LDAP connection...");
202                                    connection.close();
203                            } catch (NamingException e2) {
204                                    Log.warn("Could not close LDAP connection.");
205                            }
206                    }
207    
208                    traceEnd("null");
209                    return null;
210            }
211            
212            /**
213             * Connect to the LDAP server using specified username and password
214             * and immediatly close the connection. The error code can be retrieved 
215             * with the connectError() method.
216             *
217             * @param bindDn       the DN to use for the connection
218             * @param bindPassword the associated password
219             * 
220             * @return true if the connection succeeded, false otherwise.
221             */     
222            protected final boolean connectAndClose(
223                            final String bindDn,
224                            final String bindPassword) {
225                    traceBegin();
226                    
227                    DirContext connection = connect(bindDn, bindPassword);
228                    if (connection != null) {
229                            try {
230                                    trace("Closing LDAP connection...");
231                                    connection.close();
232                                    return true;
233                            } catch (NamingException e) {
234                                    Log.warn("Could not close LDAP connection.");
235                                    return false;
236                            }
237                    } else {
238                            return false;
239                    }
240            }
241    }
242