001/* 002 * Copyright 2007-2018 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2018 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.Extensible; 037import com.unboundid.util.InternalUseOnly; 038import com.unboundid.util.ThreadSafety; 039import com.unboundid.util.ThreadSafetyLevel; 040 041import static com.unboundid.ldap.sdk.LDAPMessages.*; 042import static com.unboundid.util.Debug.*; 043import static com.unboundid.util.StaticUtils.*; 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<LDAPResponse>(); 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 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 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 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 = nanosToMillis(System.nanoTime() - requestTime); 302 throw new LDAPException(ResultCode.TIMEOUT, 303 ERR_SASL_BIND_CLIENT_TIMEOUT.get(waitTime, getSASLMechanismName(), 304 messageID, connection.getHostPort())); 305 } 306 307 if (response instanceof ConnectionClosedResponse) 308 { 309 final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response; 310 final String message = ccr.getMessage(); 311 if (message == null) 312 { 313 // The connection was closed while waiting for the response. 314 throw new LDAPException(ccr.getResultCode(), 315 ERR_CONN_CLOSED_WAITING_FOR_BIND_RESPONSE.get( 316 connection.getHostPort(), toString())); 317 } 318 else 319 { 320 // The connection was closed while waiting for the response. 321 throw new LDAPException(ccr.getResultCode(), 322 ERR_CONN_CLOSED_WAITING_FOR_BIND_RESPONSE_WITH_MESSAGE.get( 323 connection.getHostPort(), toString(), message)); 324 } 325 } 326 327 connection.getConnectionStatistics().incrementNumBindResponses( 328 System.nanoTime() - requestTime); 329 return (BindResult) response; 330 } 331 332 333 334 /** 335 * {@inheritDoc} 336 */ 337 @InternalUseOnly() 338 @Override() 339 public final void responseReceived(final LDAPResponse response) 340 throws LDAPException 341 { 342 try 343 { 344 responseQueue.put(response); 345 } 346 catch (final Exception e) 347 { 348 debugException(e); 349 350 if (e instanceof InterruptedException) 351 { 352 Thread.currentThread().interrupt(); 353 } 354 355 throw new LDAPException(ResultCode.LOCAL_ERROR, 356 ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e); 357 } 358 } 359 360 361 362 /** 363 * {@inheritDoc} 364 */ 365 @Override() 366 public void toCode(final List<String> lineList, final String requestID, 367 final int indentSpaces, final boolean includeProcessing) 368 { 369 // Create the request variable. 370 final ArrayList<ToCodeArgHelper> constructorArgs = 371 new ArrayList<ToCodeArgHelper>(4); 372 constructorArgs.add(ToCodeArgHelper.createString(null, "Bind DN")); 373 constructorArgs.add(ToCodeArgHelper.createString(getSASLMechanismName(), 374 "SASL Mechanism Name")); 375 constructorArgs.add(ToCodeArgHelper.createByteArray( 376 "---redacted-SASL-credentials".getBytes(StandardCharsets.UTF_8), true, 377 "SASL Credentials")); 378 379 final Control[] controls = getControls(); 380 if (controls.length > 0) 381 { 382 constructorArgs.add(ToCodeArgHelper.createControlArray(controls, 383 "Bind Controls")); 384 } 385 386 ToCodeHelper.generateMethodCall(lineList, indentSpaces, 387 "GenericSASLBindRequest", requestID + "Request", 388 "new GenericSASLBindRequest", constructorArgs); 389 390 391 // Add lines for processing the request and obtaining the result. 392 if (includeProcessing) 393 { 394 // Generate a string with the appropriate indent. 395 final StringBuilder buffer = new StringBuilder(); 396 for (int i=0; i < indentSpaces; i++) 397 { 398 buffer.append(' '); 399 } 400 final String indent = buffer.toString(); 401 402 lineList.add(""); 403 lineList.add(indent + '{'); 404 lineList.add(indent + " BindResult " + requestID + 405 "Result = connection.bind(" + requestID + "Request);"); 406 lineList.add(indent + " // The bind was processed successfully."); 407 lineList.add(indent + '}'); 408 lineList.add(indent + "catch (SASLBindInProgressException e)"); 409 lineList.add(indent + '{'); 410 lineList.add(indent + " // The SASL bind requires multiple stages. " + 411 "Continue it here."); 412 lineList.add(indent + " // Do not attempt to use the connection for " + 413 "any other purpose until bind processing has completed."); 414 lineList.add(indent + '}'); 415 lineList.add(indent + "catch (LDAPException e)"); 416 lineList.add(indent + '{'); 417 lineList.add(indent + " // The bind failed. Maybe the following will " + 418 "help explain why."); 419 lineList.add(indent + " // Note that the connection is now likely in " + 420 "an unauthenticated state."); 421 lineList.add(indent + " ResultCode resultCode = e.getResultCode();"); 422 lineList.add(indent + " String message = e.getMessage();"); 423 lineList.add(indent + " String matchedDN = e.getMatchedDN();"); 424 lineList.add(indent + " String[] referralURLs = e.getReferralURLs();"); 425 lineList.add(indent + " Control[] responseControls = " + 426 "e.getResponseControls();"); 427 lineList.add(indent + '}'); 428 } 429 } 430}