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 */ 021package com.unboundid.ldap.sdk; 022 023 024 025import java.util.ArrayList; 026import java.util.List; 027import java.util.concurrent.LinkedBlockingQueue; 028import java.util.concurrent.TimeUnit; 029 030import com.unboundid.asn1.ASN1OctetString; 031import com.unboundid.ldap.protocol.BindRequestProtocolOp; 032import com.unboundid.ldap.protocol.LDAPMessage; 033import com.unboundid.ldap.protocol.LDAPResponse; 034import com.unboundid.util.Extensible; 035import com.unboundid.util.InternalUseOnly; 036import com.unboundid.util.ThreadSafety; 037import com.unboundid.util.ThreadSafetyLevel; 038 039import static com.unboundid.ldap.sdk.LDAPMessages.*; 040import static com.unboundid.util.Debug.*; 041import 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) 055public 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}