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