001 /*
002 * Copyright 2007-2016 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2016 UnboundID Corp.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021 package com.unboundid.ldap.sdk;
022
023
024
025 import java.util.ArrayList;
026 import java.util.List;
027 import java.util.concurrent.LinkedBlockingQueue;
028 import java.util.concurrent.TimeUnit;
029
030 import com.unboundid.asn1.ASN1OctetString;
031 import com.unboundid.ldap.protocol.BindRequestProtocolOp;
032 import com.unboundid.ldap.protocol.LDAPMessage;
033 import com.unboundid.ldap.protocol.LDAPResponse;
034 import com.unboundid.util.Extensible;
035 import com.unboundid.util.InternalUseOnly;
036 import com.unboundid.util.ThreadSafety;
037 import com.unboundid.util.ThreadSafetyLevel;
038
039 import static com.unboundid.ldap.sdk.LDAPMessages.*;
040 import static com.unboundid.util.Debug.*;
041 import static com.unboundid.util.StaticUtils.*;
042
043
044
045 /**
046 * This class provides an API that should be used to represent an LDAPv3 SASL
047 * bind request. A SASL bind includes a SASL mechanism name and an optional set
048 * of credentials.
049 * <BR><BR>
050 * See <A HREF="http://www.ietf.org/rfc/rfc4422.txt">RFC 4422</A> for more
051 * information about the Simple Authentication and Security Layer.
052 */
053 @Extensible()
054 @ThreadSafety(level=ThreadSafetyLevel.INTERFACE_NOT_THREADSAFE)
055 public abstract class SASLBindRequest
056 extends BindRequest
057 implements ResponseAcceptor
058 {
059 /**
060 * The BER type to use for the credentials element in a simple bind request
061 * protocol op.
062 */
063 protected static final byte CRED_TYPE_SASL = (byte) 0xA3;
064
065
066
067 /**
068 * The serial version UID for this serializable class.
069 */
070 private static final long serialVersionUID = -5842126553864908312L;
071
072
073
074 // The message ID to use for LDAP messages used in bind processing.
075 private int messageID;
076
077 // The queue used to receive responses from the server.
078 private final LinkedBlockingQueue<LDAPResponse> responseQueue;
079
080
081
082 /**
083 * Creates a new SASL bind request with the provided controls.
084 *
085 * @param controls The set of controls to include in this SASL bind request.
086 */
087 protected SASLBindRequest(final Control[] controls)
088 {
089 super(controls);
090
091 messageID = -1;
092 responseQueue = new LinkedBlockingQueue<LDAPResponse>();
093 }
094
095
096
097 /**
098 * {@inheritDoc}
099 */
100 @Override()
101 public String getBindType()
102 {
103 return getSASLMechanismName();
104 }
105
106
107
108 /**
109 * Retrieves the name of the SASL mechanism used in this SASL bind request.
110 *
111 * @return The name of the SASL mechanism used in this SASL bind request.
112 */
113 public abstract String getSASLMechanismName();
114
115
116
117 /**
118 * {@inheritDoc}
119 */
120 @Override()
121 public int getLastMessageID()
122 {
123 return messageID;
124 }
125
126
127
128 /**
129 * Sends an LDAP message to the directory server and waits for the response.
130 *
131 * @param connection The connection to the directory server.
132 * @param bindDN The bind DN to use for the request. It should be
133 * {@code null} for most types of SASL bind requests.
134 * @param saslCredentials The SASL credentials to use for the bind request.
135 * It may be {@code null} if no credentials are
136 * required.
137 * @param controls The set of controls to include in the request. It
138 * may be {@code null} if no controls are required.
139 * @param timeoutMillis The maximum length of time in milliseconds to wait
140 * for a response, or zero if it should wait forever.
141 *
142 * @return The bind response message returned by the directory server.
143 *
144 * @throws LDAPException If a problem occurs while sending the request or
145 * reading the response, or if a timeout occurred
146 * while waiting for the response.
147 */
148 protected final BindResult sendBindRequest(final LDAPConnection connection,
149 final String bindDN,
150 final ASN1OctetString saslCredentials,
151 final Control[] controls,
152 final long timeoutMillis)
153 throws LDAPException
154 {
155 if (messageID == -1)
156 {
157 messageID = connection.nextMessageID();
158 }
159
160 final BindRequestProtocolOp protocolOp =
161 new BindRequestProtocolOp(bindDN, getSASLMechanismName(),
162 saslCredentials);
163
164 final LDAPMessage requestMessage =
165 new LDAPMessage(messageID, protocolOp, controls);
166 return sendMessage(connection, requestMessage, timeoutMillis);
167 }
168
169
170
171 /**
172 * Sends an LDAP message to the directory server and waits for the response.
173 *
174 * @param connection The connection to the directory server.
175 * @param requestMessage The LDAP message to send to the directory server.
176 * @param timeoutMillis The maximum length of time in milliseconds to wait
177 * for a response, or zero if it should wait forever.
178 *
179 * @return The response message received from the server.
180 *
181 * @throws LDAPException If a problem occurs while sending the request or
182 * reading the response, or if a timeout occurred
183 * while waiting for the response.
184 */
185 protected final BindResult sendMessage(final LDAPConnection connection,
186 final LDAPMessage requestMessage,
187 final long timeoutMillis)
188 throws LDAPException
189 {
190 if (connection.synchronousMode())
191 {
192 return sendMessageSync(connection, requestMessage, timeoutMillis);
193 }
194
195 final int msgID = requestMessage.getMessageID();
196 connection.registerResponseAcceptor(msgID, this);
197 try
198 {
199 final long requestTime = System.nanoTime();
200 connection.getConnectionStatistics().incrementNumBindRequests();
201 connection.sendMessage(requestMessage);
202
203 // Wait for and process the response.
204 final LDAPResponse response;
205 try
206 {
207 if (timeoutMillis > 0)
208 {
209 response = responseQueue.poll(timeoutMillis, TimeUnit.MILLISECONDS);
210 }
211 else
212 {
213 response = responseQueue.take();
214 }
215 }
216 catch (InterruptedException ie)
217 {
218 debugException(ie);
219 throw new LDAPException(ResultCode.LOCAL_ERROR,
220 ERR_BIND_INTERRUPTED.get(connection.getHostPort()), ie);
221 }
222
223 return handleResponse(connection, response, requestTime);
224 }
225 finally
226 {
227 connection.deregisterResponseAcceptor(msgID);
228 }
229 }
230
231
232
233 /**
234 * Sends an LDAP message to the directory server and waits for the response.
235 * This should only be used when the connection is operating in synchronous
236 * mode.
237 *
238 * @param connection The connection to the directory server.
239 * @param requestMessage The LDAP message to send to the directory server.
240 * @param timeoutMillis The maximum length of time in milliseconds to wait
241 * for a response, or zero if it should wait forever.
242 *
243 * @return The response message received from the server.
244 *
245 * @throws LDAPException If a problem occurs while sending the request or
246 * reading the response, or if a timeout occurred
247 * while waiting for the response.
248 */
249 private BindResult sendMessageSync(final LDAPConnection connection,
250 final LDAPMessage requestMessage,
251 final long timeoutMillis)
252 throws LDAPException
253 {
254 // Set the appropriate timeout on the socket.
255 try
256 {
257 connection.getConnectionInternals(true).getSocket().setSoTimeout(
258 (int) timeoutMillis);
259 }
260 catch (Exception e)
261 {
262 debugException(e);
263 }
264
265
266 final int msgID = requestMessage.getMessageID();
267 final long requestTime = System.nanoTime();
268 connection.getConnectionStatistics().incrementNumBindRequests();
269 connection.sendMessage(requestMessage);
270
271 while (true)
272 {
273 final LDAPResponse response = connection.readResponse(messageID);
274 if (response instanceof IntermediateResponse)
275 {
276 final IntermediateResponseListener listener =
277 getIntermediateResponseListener();
278 if (listener != null)
279 {
280 listener.intermediateResponseReturned(
281 (IntermediateResponse) response);
282 }
283 }
284 else
285 {
286 return handleResponse(connection, response, requestTime);
287 }
288 }
289 }
290
291
292
293 /**
294 * Performs the necessary processing for handling a response.
295 *
296 * @param connection The connection used to read the response.
297 * @param response The response to be processed.
298 * @param requestTime The time the request was sent to the server.
299 *
300 * @return The bind result.
301 *
302 * @throws LDAPException If a problem occurs.
303 */
304 private BindResult handleResponse(final LDAPConnection connection,
305 final LDAPResponse response,
306 final long requestTime)
307 throws LDAPException
308 {
309 if (response == null)
310 {
311 final long waitTime = nanosToMillis(System.nanoTime() - requestTime);
312 throw new LDAPException(ResultCode.TIMEOUT,
313 ERR_SASL_BIND_CLIENT_TIMEOUT.get(waitTime, getSASLMechanismName(),
314 messageID, connection.getHostPort()));
315 }
316
317 if (response instanceof ConnectionClosedResponse)
318 {
319 final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response;
320 final String message = ccr.getMessage();
321 if (message == null)
322 {
323 // The connection was closed while waiting for the response.
324 throw new LDAPException(ccr.getResultCode(),
325 ERR_CONN_CLOSED_WAITING_FOR_BIND_RESPONSE.get(
326 connection.getHostPort(), toString()));
327 }
328 else
329 {
330 // The connection was closed while waiting for the response.
331 throw new LDAPException(ccr.getResultCode(),
332 ERR_CONN_CLOSED_WAITING_FOR_BIND_RESPONSE_WITH_MESSAGE.get(
333 connection.getHostPort(), toString(), message));
334 }
335 }
336
337 connection.getConnectionStatistics().incrementNumBindResponses(
338 System.nanoTime() - requestTime);
339 return (BindResult) response;
340 }
341
342
343
344 /**
345 * {@inheritDoc}
346 */
347 @InternalUseOnly()
348 public final void responseReceived(final LDAPResponse response)
349 throws LDAPException
350 {
351 try
352 {
353 responseQueue.put(response);
354 }
355 catch (Exception e)
356 {
357 debugException(e);
358 throw new LDAPException(ResultCode.LOCAL_ERROR,
359 ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e);
360 }
361 }
362
363
364
365 /**
366 * {@inheritDoc}
367 */
368 public void toCode(final List<String> lineList, final String requestID,
369 final int indentSpaces, final boolean includeProcessing)
370 {
371 // Create the request variable.
372 final ArrayList<ToCodeArgHelper> constructorArgs =
373 new ArrayList<ToCodeArgHelper>(4);
374 constructorArgs.add(ToCodeArgHelper.createString(null, "Bind DN"));
375 constructorArgs.add(ToCodeArgHelper.createString(getSASLMechanismName(),
376 "SASL Mechanism Name"));
377 constructorArgs.add(ToCodeArgHelper.createByteArray(
378 "---redacted-SASL-credentials".getBytes(), true,
379 "SASL Credentials"));
380
381 final Control[] controls = getControls();
382 if (controls.length > 0)
383 {
384 constructorArgs.add(ToCodeArgHelper.createControlArray(controls,
385 "Bind Controls"));
386 }
387
388 ToCodeHelper.generateMethodCall(lineList, indentSpaces,
389 "GenericSASLBindRequest", requestID + "Request",
390 "new GenericSASLBindRequest", constructorArgs);
391
392
393 // Add lines for processing the request and obtaining the result.
394 if (includeProcessing)
395 {
396 // Generate a string with the appropriate indent.
397 final StringBuilder buffer = new StringBuilder();
398 for (int i=0; i < indentSpaces; i++)
399 {
400 buffer.append(' ');
401 }
402 final String indent = buffer.toString();
403
404 lineList.add("");
405 lineList.add(indent + '{');
406 lineList.add(indent + " BindResult " + requestID +
407 "Result = connection.bind(" + requestID + "Request);");
408 lineList.add(indent + " // The bind was processed successfully.");
409 lineList.add(indent + '}');
410 lineList.add(indent + "catch (SASLBindInProgressException e)");
411 lineList.add(indent + '{');
412 lineList.add(indent + " // The SASL bind requires multiple stages. " +
413 "Continue it here.");
414 lineList.add(indent + " // Do not attempt to use the connection for " +
415 "any other purpose until bind processing has completed.");
416 lineList.add(indent + '}');
417 lineList.add(indent + "catch (LDAPException e)");
418 lineList.add(indent + '{');
419 lineList.add(indent + " // The bind failed. Maybe the following will " +
420 "help explain why.");
421 lineList.add(indent + " // Note that the connection is now likely in " +
422 "an unauthenticated state.");
423 lineList.add(indent + " ResultCode resultCode = e.getResultCode();");
424 lineList.add(indent + " String message = e.getMessage();");
425 lineList.add(indent + " String matchedDN = e.getMatchedDN();");
426 lineList.add(indent + " String[] referralURLs = e.getReferralURLs();");
427 lineList.add(indent + " Control[] responseControls = " +
428 "e.getResponseControls();");
429 lineList.add(indent + '}');
430 }
431 }
432 }