001 /*
002 * Copyright 2007-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2017 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 Thread.currentThread().interrupt();
220 throw new LDAPException(ResultCode.LOCAL_ERROR,
221 ERR_BIND_INTERRUPTED.get(connection.getHostPort()), ie);
222 }
223
224 return handleResponse(connection, response, requestTime);
225 }
226 finally
227 {
228 connection.deregisterResponseAcceptor(msgID);
229 }
230 }
231
232
233
234 /**
235 * Sends an LDAP message to the directory server and waits for the response.
236 * This should only be used when the connection is operating in synchronous
237 * mode.
238 *
239 * @param connection The connection to the directory server.
240 * @param requestMessage The LDAP message to send to the directory server.
241 * @param timeoutMillis The maximum length of time in milliseconds to wait
242 * for a response, or zero if it should wait forever.
243 *
244 * @return The response message received from the server.
245 *
246 * @throws LDAPException If a problem occurs while sending the request or
247 * reading the response, or if a timeout occurred
248 * while waiting for the response.
249 */
250 private BindResult sendMessageSync(final LDAPConnection connection,
251 final LDAPMessage requestMessage,
252 final long timeoutMillis)
253 throws LDAPException
254 {
255 // Set the appropriate timeout on the socket.
256 try
257 {
258 connection.getConnectionInternals(true).getSocket().setSoTimeout(
259 (int) timeoutMillis);
260 }
261 catch (Exception e)
262 {
263 debugException(e);
264 }
265
266
267 final int msgID = requestMessage.getMessageID();
268 final long requestTime = System.nanoTime();
269 connection.getConnectionStatistics().incrementNumBindRequests();
270 connection.sendMessage(requestMessage);
271
272 while (true)
273 {
274 final LDAPResponse response = connection.readResponse(messageID);
275 if (response instanceof IntermediateResponse)
276 {
277 final IntermediateResponseListener listener =
278 getIntermediateResponseListener();
279 if (listener != null)
280 {
281 listener.intermediateResponseReturned(
282 (IntermediateResponse) response);
283 }
284 }
285 else
286 {
287 return handleResponse(connection, response, requestTime);
288 }
289 }
290 }
291
292
293
294 /**
295 * Performs the necessary processing for handling a response.
296 *
297 * @param connection The connection used to read the response.
298 * @param response The response to be processed.
299 * @param requestTime The time the request was sent to the server.
300 *
301 * @return The bind result.
302 *
303 * @throws LDAPException If a problem occurs.
304 */
305 private BindResult handleResponse(final LDAPConnection connection,
306 final LDAPResponse response,
307 final long requestTime)
308 throws LDAPException
309 {
310 if (response == null)
311 {
312 final long waitTime = nanosToMillis(System.nanoTime() - requestTime);
313 throw new LDAPException(ResultCode.TIMEOUT,
314 ERR_SASL_BIND_CLIENT_TIMEOUT.get(waitTime, getSASLMechanismName(),
315 messageID, connection.getHostPort()));
316 }
317
318 if (response instanceof ConnectionClosedResponse)
319 {
320 final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response;
321 final String message = ccr.getMessage();
322 if (message == null)
323 {
324 // The connection was closed while waiting for the response.
325 throw new LDAPException(ccr.getResultCode(),
326 ERR_CONN_CLOSED_WAITING_FOR_BIND_RESPONSE.get(
327 connection.getHostPort(), toString()));
328 }
329 else
330 {
331 // The connection was closed while waiting for the response.
332 throw new LDAPException(ccr.getResultCode(),
333 ERR_CONN_CLOSED_WAITING_FOR_BIND_RESPONSE_WITH_MESSAGE.get(
334 connection.getHostPort(), toString(), message));
335 }
336 }
337
338 connection.getConnectionStatistics().incrementNumBindResponses(
339 System.nanoTime() - requestTime);
340 return (BindResult) response;
341 }
342
343
344
345 /**
346 * {@inheritDoc}
347 */
348 @InternalUseOnly()
349 public final void responseReceived(final LDAPResponse response)
350 throws LDAPException
351 {
352 try
353 {
354 responseQueue.put(response);
355 }
356 catch (Exception e)
357 {
358 debugException(e);
359
360 if (e instanceof InterruptedException)
361 {
362 Thread.currentThread().interrupt();
363 }
364
365 throw new LDAPException(ResultCode.LOCAL_ERROR,
366 ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e);
367 }
368 }
369
370
371
372 /**
373 * {@inheritDoc}
374 */
375 public void toCode(final List<String> lineList, final String requestID,
376 final int indentSpaces, final boolean includeProcessing)
377 {
378 // Create the request variable.
379 final ArrayList<ToCodeArgHelper> constructorArgs =
380 new ArrayList<ToCodeArgHelper>(4);
381 constructorArgs.add(ToCodeArgHelper.createString(null, "Bind DN"));
382 constructorArgs.add(ToCodeArgHelper.createString(getSASLMechanismName(),
383 "SASL Mechanism Name"));
384 constructorArgs.add(ToCodeArgHelper.createByteArray(
385 "---redacted-SASL-credentials".getBytes(), true,
386 "SASL Credentials"));
387
388 final Control[] controls = getControls();
389 if (controls.length > 0)
390 {
391 constructorArgs.add(ToCodeArgHelper.createControlArray(controls,
392 "Bind Controls"));
393 }
394
395 ToCodeHelper.generateMethodCall(lineList, indentSpaces,
396 "GenericSASLBindRequest", requestID + "Request",
397 "new GenericSASLBindRequest", constructorArgs);
398
399
400 // Add lines for processing the request and obtaining the result.
401 if (includeProcessing)
402 {
403 // Generate a string with the appropriate indent.
404 final StringBuilder buffer = new StringBuilder();
405 for (int i=0; i < indentSpaces; i++)
406 {
407 buffer.append(' ');
408 }
409 final String indent = buffer.toString();
410
411 lineList.add("");
412 lineList.add(indent + '{');
413 lineList.add(indent + " BindResult " + requestID +
414 "Result = connection.bind(" + requestID + "Request);");
415 lineList.add(indent + " // The bind was processed successfully.");
416 lineList.add(indent + '}');
417 lineList.add(indent + "catch (SASLBindInProgressException e)");
418 lineList.add(indent + '{');
419 lineList.add(indent + " // The SASL bind requires multiple stages. " +
420 "Continue it here.");
421 lineList.add(indent + " // Do not attempt to use the connection for " +
422 "any other purpose until bind processing has completed.");
423 lineList.add(indent + '}');
424 lineList.add(indent + "catch (LDAPException e)");
425 lineList.add(indent + '{');
426 lineList.add(indent + " // The bind failed. Maybe the following will " +
427 "help explain why.");
428 lineList.add(indent + " // Note that the connection is now likely in " +
429 "an unauthenticated state.");
430 lineList.add(indent + " ResultCode resultCode = e.getResultCode();");
431 lineList.add(indent + " String message = e.getMessage();");
432 lineList.add(indent + " String matchedDN = e.getMatchedDN();");
433 lineList.add(indent + " String[] referralURLs = e.getReferralURLs();");
434 lineList.add(indent + " Control[] responseControls = " +
435 "e.getResponseControls();");
436 lineList.add(indent + '}');
437 }
438 }
439 }