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