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