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