001 /*
002 * Copyright 2007-2014 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2014 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.concurrent.LinkedBlockingQueue;
026 import java.util.concurrent.TimeUnit;
027
028 import com.unboundid.asn1.ASN1OctetString;
029 import com.unboundid.ldap.protocol.BindRequestProtocolOp;
030 import com.unboundid.ldap.protocol.LDAPMessage;
031 import com.unboundid.ldap.protocol.LDAPResponse;
032 import com.unboundid.util.InternalUseOnly;
033
034 import static com.unboundid.ldap.sdk.LDAPMessages.*;
035 import static com.unboundid.util.Debug.*;
036 import static com.unboundid.util.StaticUtils.*;
037
038
039
040 /**
041 * This class provides an API that should be used to represent an LDAPv3 SASL
042 * bind request. A SASL bind includes a SASL mechanism name and an optional set
043 * of credentials.
044 * <BR><BR>
045 * See <A HREF="http://www.ietf.org/rfc/rfc4422.txt">RFC 4422</A> for more
046 * information about the Simple Authentication and Security Layer.
047 */
048 public abstract class SASLBindRequest
049 extends BindRequest
050 implements ResponseAcceptor
051 {
052 /**
053 * The BER type to use for the credentials element in a simple bind request
054 * protocol op.
055 */
056 protected static final byte CRED_TYPE_SASL = (byte) 0xA3;
057
058
059
060 /**
061 * The serial version UID for this serializable class.
062 */
063 private static final long serialVersionUID = -5842126553864908312L;
064
065
066
067 // The message ID to use for LDAP messages used in bind processing.
068 private int messageID;
069
070 // The queue used to receive responses from the server.
071 private final LinkedBlockingQueue<LDAPResponse> responseQueue;
072
073
074
075 /**
076 * Creates a new SASL bind request with the provided controls.
077 *
078 * @param controls The set of controls to include in this SASL bind request.
079 */
080 protected SASLBindRequest(final Control[] controls)
081 {
082 super(controls);
083
084 messageID = -1;
085 responseQueue = new LinkedBlockingQueue<LDAPResponse>();
086 }
087
088
089
090 /**
091 * {@inheritDoc}
092 */
093 @Override()
094 public String getBindType()
095 {
096 return getSASLMechanismName();
097 }
098
099
100
101 /**
102 * Retrieves the name of the SASL mechanism used in this SASL bind request.
103 *
104 * @return The name of the SASL mechanism used in this SASL bind request.
105 */
106 public abstract String getSASLMechanismName();
107
108
109
110 /**
111 * {@inheritDoc}
112 */
113 @Override()
114 public int getLastMessageID()
115 {
116 return messageID;
117 }
118
119
120
121 /**
122 * Sends an LDAP message to the directory server and waits for the response.
123 *
124 * @param connection The connection to the directory server.
125 * @param bindDN The bind DN to use for the request. It should be
126 * {@code null} for most types of SASL bind requests.
127 * @param saslCredentials The SASL credentials to use for the bind request.
128 * It may be {@code null} if no credentials are
129 * required.
130 * @param controls The set of controls to include in the request. It
131 * may be {@code null} if no controls are required.
132 * @param timeoutMillis The maximum length of time in milliseconds to wait
133 * for a response, or zero if it should wait forever.
134 *
135 * @return The bind response message returned by the directory server.
136 *
137 * @throws LDAPException If a problem occurs while sending the request or
138 * reading the response, or if a timeout occurred
139 * while waiting for the response.
140 */
141 protected final BindResult sendBindRequest(final LDAPConnection connection,
142 final String bindDN,
143 final ASN1OctetString saslCredentials,
144 final Control[] controls,
145 final long timeoutMillis)
146 throws LDAPException
147 {
148 if (messageID == -1)
149 {
150 messageID = connection.nextMessageID();
151 }
152
153 final BindRequestProtocolOp protocolOp =
154 new BindRequestProtocolOp(bindDN, getSASLMechanismName(),
155 saslCredentials);
156
157 final LDAPMessage requestMessage =
158 new LDAPMessage(messageID, protocolOp, controls);
159 return sendMessage(connection, requestMessage, timeoutMillis);
160 }
161
162
163
164 /**
165 * Sends an LDAP message to the directory server and waits for the response.
166 *
167 * @param connection The connection to the directory server.
168 * @param requestMessage The LDAP message to send to the directory server.
169 * @param timeoutMillis The maximum length of time in milliseconds to wait
170 * for a response, or zero if it should wait forever.
171 *
172 * @return The response message received from the server.
173 *
174 * @throws LDAPException If a problem occurs while sending the request or
175 * reading the response, or if a timeout occurred
176 * while waiting for the response.
177 */
178 protected final BindResult sendMessage(final LDAPConnection connection,
179 final LDAPMessage requestMessage,
180 final long timeoutMillis)
181 throws LDAPException
182 {
183 if (connection.synchronousMode())
184 {
185 return sendMessageSync(connection, requestMessage, timeoutMillis);
186 }
187
188 final int msgID = requestMessage.getMessageID();
189 connection.registerResponseAcceptor(msgID, this);
190 try
191 {
192 final long requestTime = System.nanoTime();
193 connection.getConnectionStatistics().incrementNumBindRequests();
194 connection.sendMessage(requestMessage);
195
196 // Wait for and process the response.
197 final LDAPResponse response;
198 try
199 {
200 if (timeoutMillis > 0)
201 {
202 response = responseQueue.poll(timeoutMillis, TimeUnit.MILLISECONDS);
203 }
204 else
205 {
206 response = responseQueue.take();
207 }
208 }
209 catch (InterruptedException ie)
210 {
211 debugException(ie);
212 throw new LDAPException(ResultCode.LOCAL_ERROR,
213 ERR_BIND_INTERRUPTED.get(connection.getHostPort()), ie);
214 }
215
216 return handleResponse(connection, response, requestTime);
217 }
218 finally
219 {
220 connection.deregisterResponseAcceptor(msgID);
221 }
222 }
223
224
225
226 /**
227 * Sends an LDAP message to the directory server and waits for the response.
228 * This should only be used when the connection is operating in synchronous
229 * mode.
230 *
231 * @param connection The connection to the directory server.
232 * @param requestMessage The LDAP message to send to the directory server.
233 * @param timeoutMillis The maximum length of time in milliseconds to wait
234 * for a response, or zero if it should wait forever.
235 *
236 * @return The response message received from the server.
237 *
238 * @throws LDAPException If a problem occurs while sending the request or
239 * reading the response, or if a timeout occurred
240 * while waiting for the response.
241 */
242 private BindResult sendMessageSync(final LDAPConnection connection,
243 final LDAPMessage requestMessage,
244 final long timeoutMillis)
245 throws LDAPException
246 {
247 // Set the appropriate timeout on the socket.
248 try
249 {
250 connection.getConnectionInternals(true).getSocket().setSoTimeout(
251 (int) timeoutMillis);
252 }
253 catch (Exception e)
254 {
255 debugException(e);
256 }
257
258
259 final int msgID = requestMessage.getMessageID();
260 final long requestTime = System.nanoTime();
261 connection.getConnectionStatistics().incrementNumBindRequests();
262 connection.sendMessage(requestMessage);
263
264 while (true)
265 {
266 final LDAPResponse response = connection.readResponse(messageID);
267 if (response instanceof IntermediateResponse)
268 {
269 final IntermediateResponseListener listener =
270 getIntermediateResponseListener();
271 if (listener != null)
272 {
273 listener.intermediateResponseReturned(
274 (IntermediateResponse) response);
275 }
276 }
277 else
278 {
279 return handleResponse(connection, response, requestTime);
280 }
281 }
282 }
283
284
285
286 /**
287 * Performs the necessary processing for handling a response.
288 *
289 * @param connection The connection used to read the response.
290 * @param response The response to be processed.
291 * @param requestTime The time the request was sent to the server.
292 *
293 * @return The bind result.
294 *
295 * @throws LDAPException If a problem occurs.
296 */
297 private BindResult handleResponse(final LDAPConnection connection,
298 final LDAPResponse response,
299 final long requestTime)
300 throws LDAPException
301 {
302 if (response == null)
303 {
304 final long waitTime = nanosToMillis(System.nanoTime() - requestTime);
305 throw new LDAPException(ResultCode.TIMEOUT,
306 ERR_SASL_BIND_CLIENT_TIMEOUT.get(waitTime, getSASLMechanismName(),
307 messageID, connection.getHostPort()));
308 }
309
310 if (response instanceof ConnectionClosedResponse)
311 {
312 final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response;
313 final String message = ccr.getMessage();
314 if (message == null)
315 {
316 // The connection was closed while waiting for the response.
317 throw new LDAPException(ccr.getResultCode(),
318 ERR_CONN_CLOSED_WAITING_FOR_BIND_RESPONSE.get(
319 connection.getHostPort(), toString()));
320 }
321 else
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_WITH_MESSAGE.get(
326 connection.getHostPort(), toString(), message));
327 }
328 }
329
330 connection.getConnectionStatistics().incrementNumBindResponses(
331 System.nanoTime() - requestTime);
332 return (BindResult) response;
333 }
334
335
336
337 /**
338 * {@inheritDoc}
339 */
340 @InternalUseOnly()
341 public final void responseReceived(final LDAPResponse response)
342 throws LDAPException
343 {
344 try
345 {
346 responseQueue.put(response);
347 }
348 catch (Exception e)
349 {
350 debugException(e);
351 throw new LDAPException(ResultCode.LOCAL_ERROR,
352 ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e);
353 }
354 }
355 }