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