001/* 002 * Copyright 2007-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2007-2024 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2007-2024 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.ldap.sdk; 037 038 039 040import java.nio.charset.StandardCharsets; 041import java.util.ArrayList; 042import java.util.List; 043import java.util.concurrent.LinkedBlockingQueue; 044import java.util.concurrent.TimeUnit; 045import java.util.logging.Level; 046 047import com.unboundid.asn1.ASN1OctetString; 048import com.unboundid.ldap.protocol.BindRequestProtocolOp; 049import com.unboundid.ldap.protocol.LDAPMessage; 050import com.unboundid.ldap.protocol.LDAPResponse; 051import com.unboundid.util.Debug; 052import com.unboundid.util.Extensible; 053import com.unboundid.util.InternalUseOnly; 054import com.unboundid.util.NotNull; 055import com.unboundid.util.Nullable; 056import com.unboundid.util.StaticUtils; 057import com.unboundid.util.ThreadSafety; 058import com.unboundid.util.ThreadSafetyLevel; 059 060import static com.unboundid.ldap.sdk.LDAPMessages.*; 061 062 063 064/** 065 * This class provides an API that should be used to represent an LDAPv3 SASL 066 * bind request. A SASL bind includes a SASL mechanism name and an optional set 067 * of credentials. 068 * <BR><BR> 069 * See <A HREF="http://www.ietf.org/rfc/rfc4422.txt">RFC 4422</A> for more 070 * information about the Simple Authentication and Security Layer. 071 */ 072@Extensible() 073@ThreadSafety(level=ThreadSafetyLevel.INTERFACE_NOT_THREADSAFE) 074public abstract class SASLBindRequest 075 extends BindRequest 076 implements ResponseAcceptor 077{ 078 /** 079 * The BER type to use for the credentials element in a simple bind request 080 * protocol op. 081 */ 082 protected static final byte CRED_TYPE_SASL = (byte) 0xA3; 083 084 085 086 /** 087 * The serial version UID for this serializable class. 088 */ 089 private static final long serialVersionUID = -5842126553864908312L; 090 091 092 093 // The message ID to use for LDAP messages used in bind processing. 094 private int messageID; 095 096 // The queue used to receive responses from the server. 097 @NotNull private final LinkedBlockingQueue<LDAPResponse> responseQueue; 098 099 100 101 /** 102 * Creates a new SASL bind request with the provided controls. 103 * 104 * @param controls The set of controls to include in this SASL bind request. 105 */ 106 protected SASLBindRequest(@Nullable final Control[] controls) 107 { 108 super(controls); 109 110 messageID = -1; 111 responseQueue = new LinkedBlockingQueue<>(); 112 } 113 114 115 116 /** 117 * {@inheritDoc} 118 */ 119 @Override() 120 @NotNull() 121 public String getBindType() 122 { 123 return getSASLMechanismName(); 124 } 125 126 127 128 /** 129 * Retrieves the name of the SASL mechanism used in this SASL bind request. 130 * 131 * @return The name of the SASL mechanism used in this SASL bind request. 132 */ 133 @NotNull() 134 public abstract String getSASLMechanismName(); 135 136 137 138 /** 139 * {@inheritDoc} 140 */ 141 @Override() 142 public int getLastMessageID() 143 { 144 return messageID; 145 } 146 147 148 149 /** 150 * Sends an LDAP message to the directory server and waits for the response. 151 * 152 * @param connection The connection to the directory server. 153 * @param bindDN The bind DN to use for the request. It should be 154 * {@code null} for most types of SASL bind requests. 155 * @param saslCredentials The SASL credentials to use for the bind request. 156 * It may be {@code null} if no credentials are 157 * required. 158 * @param controls The set of controls to include in the request. It 159 * may be {@code null} if no controls are required. 160 * @param timeoutMillis The maximum length of time in milliseconds to wait 161 * for a response, or zero if it should wait forever. 162 * 163 * @return The bind response message returned by the directory server. 164 * 165 * @throws LDAPException If a problem occurs while sending the request or 166 * reading the response, or if a timeout occurred 167 * while waiting for the response. 168 */ 169 @NotNull() 170 protected final BindResult sendBindRequest( 171 @NotNull final LDAPConnection connection, 172 @Nullable final String bindDN, 173 @Nullable final ASN1OctetString saslCredentials, 174 @Nullable final Control[] controls, 175 final long timeoutMillis) 176 throws LDAPException 177 { 178 messageID = connection.nextMessageID(); 179 180 final BindRequestProtocolOp protocolOp = 181 new BindRequestProtocolOp(bindDN, getSASLMechanismName(), 182 saslCredentials); 183 184 final LDAPMessage requestMessage = 185 new LDAPMessage(messageID, protocolOp, controls); 186 return sendMessage(connection, requestMessage, timeoutMillis); 187 } 188 189 190 191 /** 192 * Sends an LDAP message to the directory server and waits for the response. 193 * 194 * @param connection The connection to the directory server. 195 * @param requestMessage The LDAP message to send to the directory server. 196 * @param timeoutMillis The maximum length of time in milliseconds to wait 197 * for a response, or zero if it should wait forever. 198 * 199 * @return The response message received from the server. 200 * 201 * @throws LDAPException If a problem occurs while sending the request or 202 * reading the response, or if a timeout occurred 203 * while waiting for the response. 204 */ 205 @NotNull() 206 protected final BindResult sendMessage( 207 @NotNull final LDAPConnection connection, 208 @NotNull final LDAPMessage requestMessage, 209 final long timeoutMillis) 210 throws LDAPException 211 { 212 if (connection.synchronousMode()) 213 { 214 return sendMessageSync(connection, requestMessage, timeoutMillis); 215 } 216 217 final int msgID = requestMessage.getMessageID(); 218 connection.registerResponseAcceptor(msgID, this); 219 try 220 { 221 Debug.debugLDAPRequest(Level.INFO, this, msgID, connection); 222 223 final LDAPConnectionLogger logger = 224 connection.getConnectionOptions().getConnectionLogger(); 225 if (logger != null) 226 { 227 logger.logBindRequest(connection, messageID, this); 228 } 229 230 final long requestTime = System.nanoTime(); 231 connection.getConnectionStatistics().incrementNumBindRequests(); 232 connection.sendMessage(requestMessage, timeoutMillis); 233 234 // Wait for and process the response. 235 final LDAPResponse response; 236 try 237 { 238 if (timeoutMillis > 0) 239 { 240 response = responseQueue.poll(timeoutMillis, TimeUnit.MILLISECONDS); 241 } 242 else 243 { 244 response = responseQueue.take(); 245 } 246 } 247 catch (final InterruptedException ie) 248 { 249 Debug.debugException(ie); 250 Thread.currentThread().interrupt(); 251 throw new LDAPException(ResultCode.LOCAL_ERROR, 252 ERR_BIND_INTERRUPTED.get(connection.getHostPort()), ie); 253 } 254 255 return handleResponse(connection, response, requestTime); 256 } 257 finally 258 { 259 connection.deregisterResponseAcceptor(msgID); 260 } 261 } 262 263 264 265 /** 266 * Sends an LDAP message to the directory server and waits for the response. 267 * This should only be used when the connection is operating in synchronous 268 * mode. 269 * 270 * @param connection The connection to the directory server. 271 * @param requestMessage The LDAP message to send to the directory server. 272 * @param timeoutMillis The maximum length of time in milliseconds to wait 273 * for a response, or zero if it should wait forever. 274 * 275 * @return The response message received from the server. 276 * 277 * @throws LDAPException If a problem occurs while sending the request or 278 * reading the response, or if a timeout occurred 279 * while waiting for the response. 280 */ 281 @NotNull() 282 private BindResult sendMessageSync(@NotNull final LDAPConnection connection, 283 @NotNull final LDAPMessage requestMessage, 284 final long timeoutMillis) 285 throws LDAPException 286 { 287 final int msgID = requestMessage.getMessageID(); 288 Debug.debugLDAPRequest(Level.INFO, this, msgID, connection); 289 290 final LDAPConnectionLogger logger = 291 connection.getConnectionOptions().getConnectionLogger(); 292 if (logger != null) 293 { 294 logger.logBindRequest(connection, messageID, this); 295 } 296 297 final long requestTime = System.nanoTime(); 298 connection.getConnectionStatistics().incrementNumBindRequests(); 299 connection.sendMessage(requestMessage, timeoutMillis); 300 301 while (true) 302 { 303 final LDAPResponse response = connection.readResponse(messageID); 304 if (response instanceof IntermediateResponse) 305 { 306 final IntermediateResponseListener listener = 307 getIntermediateResponseListener(); 308 if (listener != null) 309 { 310 listener.intermediateResponseReturned( 311 (IntermediateResponse) response); 312 } 313 } 314 else 315 { 316 return handleResponse(connection, response, requestTime); 317 } 318 } 319 } 320 321 322 323 /** 324 * Performs the necessary processing for handling a response. 325 * 326 * @param connection The connection used to read the response. 327 * @param response The response to be processed. 328 * @param requestTime The time the request was sent to the server. 329 * 330 * @return The bind result. 331 * 332 * @throws LDAPException If a problem occurs. 333 */ 334 @NotNull() 335 private BindResult handleResponse(@NotNull final LDAPConnection connection, 336 @Nullable final LDAPResponse response, 337 final long requestTime) 338 throws LDAPException 339 { 340 if (response == null) 341 { 342 final long waitTime = 343 StaticUtils.nanosToMillis(System.nanoTime() - requestTime); 344 throw new LDAPException(ResultCode.TIMEOUT, 345 ERR_SASL_BIND_CLIENT_TIMEOUT.get(waitTime, getSASLMechanismName(), 346 messageID, connection.getHostPort())); 347 } 348 349 if (response instanceof ConnectionClosedResponse) 350 { 351 final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response; 352 final String message = ccr.getMessage(); 353 if (message == null) 354 { 355 // The connection was closed while waiting for the response. 356 throw new LDAPException(ccr.getResultCode(), 357 ERR_CONN_CLOSED_WAITING_FOR_BIND_RESPONSE.get( 358 connection.getHostPort(), toString())); 359 } 360 else 361 { 362 // The connection was closed while waiting for the response. 363 throw new LDAPException(ccr.getResultCode(), 364 ERR_CONN_CLOSED_WAITING_FOR_BIND_RESPONSE_WITH_MESSAGE.get( 365 connection.getHostPort(), toString(), message)); 366 } 367 } 368 369 connection.getConnectionStatistics().incrementNumBindResponses( 370 System.nanoTime() - requestTime); 371 return (BindResult) response; 372 } 373 374 375 376 /** 377 * {@inheritDoc} 378 */ 379 @InternalUseOnly() 380 @Override() 381 public final void responseReceived(@NotNull final LDAPResponse response) 382 throws LDAPException 383 { 384 try 385 { 386 responseQueue.put(response); 387 } 388 catch (final Exception e) 389 { 390 Debug.debugException(e); 391 392 if (e instanceof InterruptedException) 393 { 394 Thread.currentThread().interrupt(); 395 } 396 397 throw new LDAPException(ResultCode.LOCAL_ERROR, 398 ERR_EXCEPTION_HANDLING_RESPONSE.get( 399 StaticUtils.getExceptionMessage(e)), 400 e); 401 } 402 } 403 404 405 406 /** 407 * {@inheritDoc} 408 */ 409 @Override() 410 public void toCode(@NotNull final List<String> lineList, 411 @NotNull final String requestID, 412 final int indentSpaces, final boolean includeProcessing) 413 { 414 // Create the request variable. 415 final ArrayList<ToCodeArgHelper> constructorArgs = new ArrayList<>(4); 416 constructorArgs.add(ToCodeArgHelper.createString(null, "Bind DN")); 417 constructorArgs.add(ToCodeArgHelper.createString(getSASLMechanismName(), 418 "SASL Mechanism Name")); 419 constructorArgs.add(ToCodeArgHelper.createByteArray( 420 "---redacted-SASL-credentials".getBytes(StandardCharsets.UTF_8), true, 421 "SASL Credentials")); 422 423 final Control[] controls = getControls(); 424 if (controls.length > 0) 425 { 426 constructorArgs.add(ToCodeArgHelper.createControlArray(controls, 427 "Bind Controls")); 428 } 429 430 ToCodeHelper.generateMethodCall(lineList, indentSpaces, 431 "GenericSASLBindRequest", requestID + "Request", 432 "new GenericSASLBindRequest", constructorArgs); 433 434 435 // Add lines for processing the request and obtaining the result. 436 if (includeProcessing) 437 { 438 // Generate a string with the appropriate indent. 439 final StringBuilder buffer = new StringBuilder(); 440 for (int i=0; i < indentSpaces; i++) 441 { 442 buffer.append(' '); 443 } 444 final String indent = buffer.toString(); 445 446 lineList.add(""); 447 lineList.add(indent + '{'); 448 lineList.add(indent + " BindResult " + requestID + 449 "Result = connection.bind(" + requestID + "Request);"); 450 lineList.add(indent + " // The bind was processed successfully."); 451 lineList.add(indent + '}'); 452 lineList.add(indent + "catch (SASLBindInProgressException e)"); 453 lineList.add(indent + '{'); 454 lineList.add(indent + " // The SASL bind requires multiple stages. " + 455 "Continue it here."); 456 lineList.add(indent + " // Do not attempt to use the connection for " + 457 "any other purpose until bind processing has completed."); 458 lineList.add(indent + '}'); 459 lineList.add(indent + "catch (LDAPException e)"); 460 lineList.add(indent + '{'); 461 lineList.add(indent + " // The bind failed. Maybe the following will " + 462 "help explain why."); 463 lineList.add(indent + " // Note that the connection is now likely in " + 464 "an unauthenticated state."); 465 lineList.add(indent + " ResultCode resultCode = e.getResultCode();"); 466 lineList.add(indent + " String message = e.getMessage();"); 467 lineList.add(indent + " String matchedDN = e.getMatchedDN();"); 468 lineList.add(indent + " String[] referralURLs = e.getReferralURLs();"); 469 lineList.add(indent + " Control[] responseControls = " + 470 "e.getResponseControls();"); 471 lineList.add(indent + '}'); 472 } 473 } 474}