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