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