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