LDAPConnection.java revision 88f16d892d54fd8c3e190cc1f6363638b11ae1a3
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
* or http://forgerock.org/license/CDDLv1.0.html.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at legal-notices/CDDLv1_0.txt.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2009-2010 Sun Microsystems, Inc.
* Portions Copyright 2013-2015 ForgeRock AS
*/
package org.opends.server.tools;
import java.io.IOException;
import java.io.PrintStream;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import org.forgerock.i18n.LocalizableMessage;
import org.opends.server.controls.AuthorizationIdentityResponseControl;
import org.opends.server.controls.PasswordExpiringControl;
import org.opends.server.controls.PasswordPolicyErrorType;
import org.opends.server.controls.PasswordPolicyResponseControl;
import org.opends.server.controls.PasswordPolicyWarningType;
import org.opends.server.loggers.JDKLogging;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.opends.server.protocols.ldap.ExtendedRequestProtocolOp;
import org.opends.server.protocols.ldap.ExtendedResponseProtocolOp;
import org.opends.server.protocols.ldap.LDAPControl;
import org.opends.server.protocols.ldap.LDAPMessage;
import org.opends.server.protocols.ldap.UnbindRequestProtocolOp;
import org.forgerock.opendj.ldap.ByteString;
import org.opends.server.types.Control;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.LDAPException;
import com.forgerock.opendj.cli.ClientException;
import static org.opends.messages.CoreMessages.*;
import static org.opends.messages.ToolMessages.*;
import static org.opends.server.protocols.ldap.LDAPResultCode.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
/**
* This class provides a tool that can be used to issue search requests to the
* Directory Server.
*/
public class LDAPConnection
{
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
// The hostname to connect to.
private String hostName;
// The port number on which the directory server is accepting requests.
private int portNumber = 389;
private LDAPConnectionOptions connectionOptions;
private LDAPWriter ldapWriter;
private LDAPReader ldapReader;
private int versionNumber = 3;
private final PrintStream out;
private final PrintStream err;
/**
* Constructor for the LDAPConnection object.
*
* @param host The hostname to send the request to.
* @param port The port number on which the directory server is accepting
* requests.
* @param options The set of options for this connection.
*/
public LDAPConnection(String host, int port, LDAPConnectionOptions options)
{
this(host, port, options, System.out, System.err);
}
/**
* Constructor for the LDAPConnection object.
*
* @param host The hostname to send the request to.
* @param port The port number on which the directory server is accepting
* requests.
* @param options The set of options for this connection.
* @param out The print stream to use for standard output.
* @param err The print stream to use for standard error.
*/
public LDAPConnection(String host, int port, LDAPConnectionOptions options,
PrintStream out, PrintStream err)
{
this.hostName = host;
this.portNumber = port;
this.connectionOptions = options;
this.versionNumber = options.getVersionNumber();
this.out = out;
this.err = err;
}
/**
* Connects to the directory server instance running on specified hostname
* and port number.
*
* @param bindDN The DN to bind with.
* @param bindPassword The password to bind with.
*
* @throws LDAPConnectionException If a problem occurs while attempting to
* establish the connection to the server.
*/
public void connectToHost(String bindDN, String bindPassword)
throws LDAPConnectionException
{
connectToHost(bindDN, bindPassword, new AtomicInteger(1));
}
/**
* Connects to the directory server instance running on specified hostname
* and port number.
*
* @param bindDN The DN to bind with.
* @param bindPassword The password to bind with.
* @param nextMessageID The message ID counter that should be used for
* operations performed while establishing the
* connection.
*
* @throws LDAPConnectionException If a problem occurs while attempting to
* establish the connection to the server.
*/
public void connectToHost(String bindDN, String bindPassword,
AtomicInteger nextMessageID)
throws LDAPConnectionException
{
connectToHost(bindDN, bindPassword, nextMessageID, 0);
}
/**
* Connects to the directory server instance running on specified hostname
* and port number.
*
* @param bindDN The DN to bind with.
* @param bindPassword The password to bind with.
* @param nextMessageID The message ID counter that should be used for
* operations performed while establishing the
* connection.
* @param timeout The timeout to connect to the specified host. The
* timeout is the timeout at the socket level in
* milliseconds. If the timeout value is {@code 0},
* no timeout is used.
*
* @throws LDAPConnectionException If a problem occurs while attempting to
* establish the connection to the server.
*/
public void connectToHost(String bindDN, String bindPassword,
AtomicInteger nextMessageID, int timeout)
throws LDAPConnectionException
{
Socket socket;
Socket startTLSSocket = null;
int resultCode;
ArrayList<Control> requestControls = new ArrayList<Control> ();
ArrayList<Control> responseControls = new ArrayList<Control> ();
if (connectionOptions.isVerbose())
{
JDKLogging.enableConsoleLoggingForOpenDJ(Level.ALL);
}
else
{
JDKLogging.disableLogging();
}
if(connectionOptions.useStartTLS())
{
try
{
startTLSSocket = createSocket();
ldapWriter = new LDAPWriter(startTLSSocket);
ldapReader = new LDAPReader(startTLSSocket);
}
catch (LDAPConnectionException e)
{
throw e;
}
catch (Exception ex)
{
logger.traceException(ex);
throw new LDAPConnectionException(LocalizableMessage.raw(ex.getMessage()), ex);
}
// Send the StartTLS extended request.
ExtendedRequestProtocolOp extendedRequest =
new ExtendedRequestProtocolOp(OID_START_TLS_REQUEST);
LDAPMessage msg = new LDAPMessage(nextMessageID.getAndIncrement(),
extendedRequest);
try
{
ldapWriter.writeMessage(msg);
// Read the response from the server.
msg = ldapReader.readMessage();
}catch (LDAPException ex1)
{
logger.traceException(ex1);
throw new LDAPConnectionException(LocalizableMessage.raw(ex1.getMessage()), ex1
.getResultCode(), null, ex1);
} catch (Exception ex1)
{
logger.traceException(ex1);
throw new LDAPConnectionException(LocalizableMessage.raw(ex1.getMessage()), ex1);
}
ExtendedResponseProtocolOp res = msg.getExtendedResponseProtocolOp();
resultCode = res.getResultCode();
if(resultCode != SUCCESS)
{
throw new LDAPConnectionException(res.getErrorMessage(),
resultCode,
res.getErrorMessage(),
res.getMatchedDN(), null);
}
}
SSLConnectionFactory sslConnectionFactory =
connectionOptions.getSSLConnectionFactory();
try
{
socket = createSSLOrBasicSocket(startTLSSocket, sslConnectionFactory);
ldapWriter = new LDAPWriter(socket);
ldapReader = new LDAPReader(socket);
} catch(UnknownHostException uhe)
{
LocalizableMessage msg = INFO_RESULT_CLIENT_SIDE_CONNECT_ERROR.get();
throw new LDAPConnectionException(msg, CLIENT_SIDE_CONNECT_ERROR, null,
uhe);
} catch(ConnectException ce)
{
LocalizableMessage msg = INFO_RESULT_CLIENT_SIDE_CONNECT_ERROR.get();
throw new LDAPConnectionException(msg, CLIENT_SIDE_CONNECT_ERROR, null,
ce);
} catch (LDAPConnectionException e)
{
throw e;
} catch(Exception ex2)
{
logger.traceException(ex2);
throw new LDAPConnectionException(LocalizableMessage.raw(ex2.getMessage()), ex2);
}
// We need this so that we don't run out of addresses when the tool
// commands are called A LOT, as in the unit tests.
try
{
socket.setSoLinger(true, 1);
socket.setReuseAddress(true);
if (timeout > 0)
{
socket.setSoTimeout(timeout);
}
} catch(IOException e)
{
logger.traceException(e);
// It doesn't matter too much if this throws, so ignore it.
}
if (connectionOptions.getReportAuthzID())
{
requestControls.add(new LDAPControl(OID_AUTHZID_REQUEST));
}
if (connectionOptions.usePasswordPolicyControl())
{
requestControls.add(new LDAPControl(OID_PASSWORD_POLICY_CONTROL));
}
LDAPAuthenticationHandler handler = new LDAPAuthenticationHandler(
ldapReader, ldapWriter, hostName, nextMessageID);
try
{
ByteString bindDNBytes;
if(bindDN == null)
{
bindDNBytes = ByteString.empty();
}
else
{
bindDNBytes = ByteString.valueOf(bindDN);
}
ByteString bindPW;
if (bindPassword == null)
{
bindPW = null;
}
else
{
bindPW = ByteString.valueOf(bindPassword);
}
String result = null;
if (connectionOptions.useSASLExternal())
{
result = handler.doSASLExternal(bindDNBytes,
connectionOptions.getSASLProperties(),
requestControls, responseControls);
}
else if (connectionOptions.getSASLMechanism() != null)
{
result = handler.doSASLBind(bindDNBytes, bindPW,
connectionOptions.getSASLMechanism(),
connectionOptions.getSASLProperties(),
requestControls, responseControls);
}
else if(bindDN != null)
{
result = handler.doSimpleBind(versionNumber, bindDNBytes, bindPW,
requestControls, responseControls);
}
if(result != null)
{
out.println(result);
}
for (Control c : responseControls)
{
if (c.getOID().equals(OID_AUTHZID_RESPONSE))
{
AuthorizationIdentityResponseControl control;
if (c instanceof LDAPControl)
{
// We have to decode this control.
control = AuthorizationIdentityResponseControl.DECODER.decode(c
.isCritical(), ((LDAPControl) c).getValue());
}
else
{
// Control should already have been decoded.
control = (AuthorizationIdentityResponseControl)c;
}
LocalizableMessage message =
INFO_BIND_AUTHZID_RETURNED.get(
control.getAuthorizationID());
out.println(message);
}
else if (c.getOID().equals(OID_NS_PASSWORD_EXPIRED))
{
LocalizableMessage message = INFO_BIND_PASSWORD_EXPIRED.get();
out.println(message);
}
else if (c.getOID().equals(OID_NS_PASSWORD_EXPIRING))
{
PasswordExpiringControl control;
if(c instanceof LDAPControl)
{
// We have to decode this control.
control = PasswordExpiringControl.DECODER.decode(c.isCritical(),
((LDAPControl) c).getValue());
}
else
{
// Control should already have been decoded.
control = (PasswordExpiringControl)c;
}
LocalizableMessage timeString =
secondsToTimeString(control.getSecondsUntilExpiration());
LocalizableMessage message = INFO_BIND_PASSWORD_EXPIRING.get(timeString);
out.println(message);
}
else if (c.getOID().equals(OID_PASSWORD_POLICY_CONTROL))
{
PasswordPolicyResponseControl pwPolicyControl;
if(c instanceof LDAPControl)
{
pwPolicyControl = PasswordPolicyResponseControl.DECODER.decode(c
.isCritical(), ((LDAPControl) c).getValue());
}
else
{
pwPolicyControl = (PasswordPolicyResponseControl)c;
}
PasswordPolicyErrorType errorType = pwPolicyControl.getErrorType();
if (errorType != null)
{
switch (errorType)
{
case PASSWORD_EXPIRED:
LocalizableMessage message = INFO_BIND_PASSWORD_EXPIRED.get();
out.println(message);
break;
case ACCOUNT_LOCKED:
message = INFO_BIND_ACCOUNT_LOCKED.get();
out.println(message);
break;
case CHANGE_AFTER_RESET:
message = INFO_BIND_MUST_CHANGE_PASSWORD.get();
out.println(message);
break;
}
}
PasswordPolicyWarningType warningType =
pwPolicyControl.getWarningType();
if (warningType != null)
{
switch (warningType)
{
case TIME_BEFORE_EXPIRATION:
LocalizableMessage timeString =
secondsToTimeString(pwPolicyControl.getWarningValue());
LocalizableMessage message = INFO_BIND_PASSWORD_EXPIRING.get(timeString);
out.println(message);
break;
case GRACE_LOGINS_REMAINING:
message = INFO_BIND_GRACE_LOGINS_REMAINING.get(
pwPolicyControl.getWarningValue());
out.println(message);
break;
}
}
}
}
} catch(ClientException ce)
{
logger.traceException(ce);
throw new LDAPConnectionException(ce.getMessageObject(), ce.getReturnCode(),
null, ce);
} catch (LDAPException le) {
throw new LDAPConnectionException(le.getMessageObject(),
le.getResultCode(),
le.getErrorMessage(),
le.getMatchedDN(),
le.getCause());
} catch (DirectoryException de)
{
throw new LDAPConnectionException(de.getMessageObject(),
de.getResultCode().intValue(), null, de.getMatchedDN(), de.getCause());
} catch(Exception ex)
{
logger.traceException(ex);
throw new LDAPConnectionException(
LocalizableMessage.raw(ex.getLocalizedMessage()),ex);
}
finally
{
if (timeout > 0)
{
try
{
socket.setSoTimeout(0);
}
catch (SocketException e)
{
e.printStackTrace();
logger.traceException(e);
}
}
}
}
/**
* Creates a socket using the hostName and portNumber encapsulated in the
* current object. For each IP address associated to this host name,
* createSocket() will try to open a socket and it will return the first
* socket for which we successfully establish a connection.
* <p>
* This method can never return null because it will receive
* UnknownHostException before and then throw LDAPConnectionException.
* </p>
*
* @return a new {@link Socket}.
* @throws LDAPConnectionException
* if any exception occurs including UnknownHostException
*/
private Socket createSocket() throws LDAPConnectionException
{
ConnectException ce = null;
try
{
for (InetAddress inetAddress : InetAddress.getAllByName(hostName))
{
try
{
return new Socket(inetAddress, portNumber);
}
catch (ConnectException ce2)
{
if (ce == null)
{
ce = ce2;
}
}
}
}
catch (UnknownHostException uhe)
{
LocalizableMessage msg = INFO_RESULT_CLIENT_SIDE_CONNECT_ERROR.get();
throw new LDAPConnectionException(msg, CLIENT_SIDE_CONNECT_ERROR, null,
uhe);
}
catch (Exception ex)
{
// if we get there, something went awfully wrong while creatng one socket,
// no need to continue the for loop.
logger.traceException(ex);
throw new LDAPConnectionException(LocalizableMessage.raw(ex.getMessage()), ex);
}
if (ce != null)
{
LocalizableMessage msg = INFO_RESULT_CLIENT_SIDE_CONNECT_ERROR.get();
throw new LDAPConnectionException(msg, CLIENT_SIDE_CONNECT_ERROR, null,
ce);
}
return null;
}
/**
* Creates an SSL socket using the hostName and portNumber encapsulated in the
* current object. For each IP address associated to this host name,
* createSSLSocket() will try to open a socket and it will return the first
* socket for which we successfully establish a connection.
* <p>
* This method can never return null because it will receive
* UnknownHostException before and then throw LDAPConnectionException.
* </p>
*
* @return a new {@link Socket}.
* @throws LDAPConnectionException
* if any exception occurs including UnknownHostException
*/
private Socket createSSLSocket(SSLConnectionFactory sslConnectionFactory)
throws SSLConnectionException, LDAPConnectionException
{
ConnectException ce = null;
try
{
for (InetAddress inetAddress : InetAddress.getAllByName(hostName))
{
try
{
return sslConnectionFactory.createSocket(inetAddress, portNumber);
}
catch (ConnectException ce2)
{
if (ce == null)
{
ce = ce2;
}
}
}
}
catch (UnknownHostException uhe)
{
LocalizableMessage msg = INFO_RESULT_CLIENT_SIDE_CONNECT_ERROR.get();
throw new LDAPConnectionException(msg, CLIENT_SIDE_CONNECT_ERROR, null,
uhe);
}
catch (Exception ex)
{
// if we get there, something went awfully wrong while creatng one socket,
// no need to continue the for loop.
logger.traceException(ex);
throw new LDAPConnectionException(LocalizableMessage.raw(ex.getMessage()), ex);
}
if (ce != null)
{
LocalizableMessage msg = INFO_RESULT_CLIENT_SIDE_CONNECT_ERROR.get();
throw new LDAPConnectionException(msg, CLIENT_SIDE_CONNECT_ERROR, null,
ce);
}
return null;
}
/**
* Creates an SSL socket or a normal/basic socket using the hostName and
* portNumber encapsulated in the current object, or with the passed in socket
* if it needs to use start TLS.
*
* @param startTLSSocket
* the Socket to use if it needs to use start TLS.
* @param sslConnectionFactory
* the {@link SSLConnectionFactory} for creating SSL sockets
* @return a new {@link Socket}
* @throws SSLConnectionException
* if the SSL socket creation fails
* @throws LDAPConnectionException
* if any other error occurs
*/
private Socket createSSLOrBasicSocket(Socket startTLSSocket,
SSLConnectionFactory sslConnectionFactory) throws SSLConnectionException,
LDAPConnectionException
{
if (sslConnectionFactory == null)
{
return createSocket();
}
else if (!connectionOptions.useStartTLS())
{
return createSSLSocket(sslConnectionFactory);
}
else
{
try
{
// Use existing socket.
return sslConnectionFactory.createSocket(startTLSSocket, hostName,
portNumber, true);
}
catch (IOException e)
{
LocalizableMessage msg = INFO_RESULT_CLIENT_SIDE_CONNECT_ERROR.get();
throw new LDAPConnectionException(msg, CLIENT_SIDE_CONNECT_ERROR, null,
e);
}
}
}
/**
* Close the underlying ASN1 reader and writer, optionally sending an unbind
* request before disconnecting.
*
* @param nextMessageID The message ID counter that should be used for
* the unbind request, or {@code null} if the
* connection should be closed without an unbind
* request.
*/
public void close(AtomicInteger nextMessageID)
{
if(ldapWriter != null)
{
if (nextMessageID != null)
{
try
{
LDAPMessage message = new LDAPMessage(nextMessageID.getAndIncrement(),
new UnbindRequestProtocolOp());
ldapWriter.writeMessage(message);
} catch (Exception e) {}
}
ldapWriter.close();
}
if(ldapReader != null)
{
ldapReader.close();
}
}
/**
* Get the underlying LDAP writer.
*
* @return The underlying LDAP writer.
*/
public LDAPWriter getLDAPWriter()
{
return ldapWriter;
}
/**
* Get the underlying LDAP reader.
*
* @return The underlying LDAP reader.
*/
public LDAPReader getLDAPReader()
{
return ldapReader;
}
}