/*
|
* 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
|
* trunk/opends/resource/legal-notices/OpenDS.LICENSE
|
* or https://OpenDS.dev.java.net/OpenDS.LICENSE.
|
* 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
|
* trunk/opends/resource/legal-notices/OpenDS.LICENSE. 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
|
*
|
*
|
* Portions Copyright 2007 Sun Microsystems, Inc.
|
*/
|
package org.opends.server.tools;
|
import org.opends.messages.Message;
|
|
import java.io.PrintStream;
|
import java.io.IOException;
|
import java.net.ConnectException;
|
import java.net.Socket;
|
import java.net.UnknownHostException;
|
import java.util.ArrayList;
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
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.protocols.asn1.ASN1OctetString;
|
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.opends.server.types.Control;
|
import org.opends.server.types.DebugLogLevel;
|
import org.opends.server.types.LDAPException;
|
|
import static org.opends.server.loggers.debug.DebugLogger.*;
|
import org.opends.server.loggers.debug.DebugTracer;
|
import static org.opends.messages.CoreMessages.
|
INFO_RESULT_CLIENT_SIDE_CONNECT_ERROR;
|
import static org.opends.messages.ToolMessages.*;
|
import static org.opends.server.util.ServerConstants.*;
|
import static org.opends.server.util.StaticUtils.*;
|
import static org.opends.server.protocols.ldap.LDAPResultCode.*;
|
|
|
|
/**
|
* This class provides a tool that can be used to issue search requests to the
|
* Directory Server.
|
*/
|
public class LDAPConnection
|
{
|
/**
|
* The tracer object for the debug logger.
|
*/
|
private static final DebugTracer TRACER = getTracer();
|
|
// The hostname to connect to.
|
private String hostName = null;
|
|
// The port number on which the directory server is accepting requests.
|
private int portNumber = 389;
|
|
private LDAPConnectionOptions connectionOptions = null;
|
private LDAPWriter ldapWriter;
|
private LDAPReader ldapReader;
|
private int versionNumber = 3;
|
|
private PrintStream out;
|
private 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
|
{
|
Socket socket;
|
Socket startTLSSocket = null;
|
int resultCode;
|
ArrayList<LDAPControl> requestControls = new ArrayList<LDAPControl> ();
|
ArrayList<LDAPControl> responseControls = new ArrayList<LDAPControl> ();
|
|
VerboseTracer tracer =
|
new VerboseTracer(connectionOptions.isVerbose(), err);
|
if(connectionOptions.useStartTLS())
|
{
|
try
|
{
|
startTLSSocket = new Socket(hostName, portNumber);
|
ldapWriter = new LDAPWriter(startTLSSocket, tracer);
|
ldapReader = new LDAPReader(startTLSSocket, tracer);
|
} catch(UnknownHostException uhe)
|
{
|
Message msg = INFO_RESULT_CLIENT_SIDE_CONNECT_ERROR.get();
|
throw new LDAPConnectionException(msg, CLIENT_SIDE_CONNECT_ERROR, null,
|
uhe);
|
} catch(ConnectException ce)
|
{
|
Message msg = INFO_RESULT_CLIENT_SIDE_CONNECT_ERROR.get();
|
throw new LDAPConnectionException(msg, CLIENT_SIDE_CONNECT_ERROR, null,
|
ce);
|
} catch(Exception ex)
|
{
|
if (debugEnabled())
|
{
|
TRACER.debugCaught(DebugLogLevel.ERROR, ex);
|
}
|
throw new LDAPConnectionException(Message.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 (Exception ex1)
|
{
|
if (debugEnabled())
|
{
|
TRACER.debugCaught(DebugLogLevel.ERROR, ex1);
|
}
|
throw new LDAPConnectionException(Message.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
|
{
|
if(sslConnectionFactory != null)
|
{
|
if(connectionOptions.useStartTLS())
|
{
|
// Use existing socket.
|
socket = sslConnectionFactory.createSocket(startTLSSocket, hostName,
|
portNumber, true);
|
} else
|
{
|
socket = sslConnectionFactory.createSocket(hostName, portNumber);
|
}
|
} else
|
{
|
socket = new Socket(hostName, portNumber);
|
}
|
ldapWriter = new LDAPWriter(socket, tracer);
|
ldapReader = new LDAPReader(socket, tracer);
|
} catch(UnknownHostException uhe)
|
{
|
Message msg = INFO_RESULT_CLIENT_SIDE_CONNECT_ERROR.get();
|
throw new LDAPConnectionException(msg, CLIENT_SIDE_CONNECT_ERROR, null,
|
uhe);
|
} catch(ConnectException ce)
|
{
|
Message msg = INFO_RESULT_CLIENT_SIDE_CONNECT_ERROR.get();
|
throw new LDAPConnectionException(msg, CLIENT_SIDE_CONNECT_ERROR, null,
|
ce);
|
} catch(Exception ex2)
|
{
|
if (debugEnabled())
|
{
|
TRACER.debugCaught(DebugLogLevel.ERROR, ex2);
|
}
|
throw new LDAPConnectionException(Message.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);
|
} catch(IOException e)
|
{
|
if (debugEnabled())
|
{
|
TRACER.debugCaught(DebugLogLevel.ERROR, 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
|
{
|
ASN1OctetString bindPW;
|
if (bindPassword == null)
|
{
|
bindPW = null;
|
}
|
else
|
{
|
bindPW = new ASN1OctetString(bindPassword);
|
}
|
|
String result = null;
|
if (connectionOptions.useSASLExternal())
|
{
|
result = handler.doSASLExternal(new ASN1OctetString(bindDN),
|
connectionOptions.getSASLProperties(),
|
requestControls, responseControls);
|
}
|
else if (connectionOptions.getSASLMechanism() != null)
|
{
|
result = handler.doSASLBind(new ASN1OctetString(bindDN), bindPW,
|
connectionOptions.getSASLMechanism(),
|
connectionOptions.getSASLProperties(),
|
requestControls, responseControls);
|
}
|
else if(bindDN != null)
|
{
|
result = handler.doSimpleBind(versionNumber,
|
new ASN1OctetString(bindDN), bindPW,
|
requestControls, responseControls);
|
}
|
if(result != null)
|
{
|
out.println(result);
|
}
|
|
for (LDAPControl c : responseControls)
|
{
|
if (c.getOID().equals(OID_AUTHZID_RESPONSE))
|
{
|
ASN1OctetString controlValue = c.getValue();
|
if (controlValue != null)
|
{
|
|
Message message =
|
INFO_BIND_AUTHZID_RETURNED.get(controlValue.stringValue());
|
out.println(message);
|
}
|
}
|
else if (c.getOID().equals(OID_NS_PASSWORD_EXPIRED))
|
{
|
|
Message message = INFO_BIND_PASSWORD_EXPIRED.get();
|
out.println(message);
|
}
|
else if (c.getOID().equals(OID_NS_PASSWORD_EXPIRING))
|
{
|
PasswordExpiringControl expiringControl =
|
PasswordExpiringControl.decodeControl(new Control(c.getOID(),
|
c.isCritical(),
|
c.getValue()));
|
Message timeString =
|
secondsToTimeString(expiringControl.getSecondsUntilExpiration());
|
|
|
Message message = INFO_BIND_PASSWORD_EXPIRING.get(timeString);
|
out.println(message);
|
}
|
else if (c.getOID().equals(OID_PASSWORD_POLICY_CONTROL))
|
{
|
PasswordPolicyResponseControl pwPolicyControl =
|
PasswordPolicyResponseControl.decodeControl(new Control(
|
c.getOID(), c.isCritical(), c.getValue()));
|
|
PasswordPolicyErrorType errorType = pwPolicyControl.getErrorType();
|
if (errorType != null)
|
{
|
switch (errorType)
|
{
|
case PASSWORD_EXPIRED:
|
|
Message 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:
|
Message timeString =
|
secondsToTimeString(pwPolicyControl.getWarningValue());
|
|
|
Message 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)
|
{
|
if (debugEnabled())
|
{
|
TRACER.debugCaught(DebugLogLevel.ERROR, ce);
|
}
|
throw new LDAPConnectionException(ce.getMessageObject(), ce.getExitCode(),
|
null, ce);
|
} catch (LDAPException le)
|
{
|
throw new LDAPConnectionException(le.getMessageObject(),
|
le.getResultCode(),
|
le.getErrorMessage(),
|
le.getMatchedDN(),
|
le.getCause());
|
} catch(Exception ex)
|
{
|
if (debugEnabled())
|
{
|
TRACER.debugCaught(DebugLogLevel.ERROR, ex);
|
}
|
throw new LDAPConnectionException(
|
Message.raw(ex.getLocalizedMessage()),ex);
|
}
|
|
}
|
|
/**
|
* 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;
|
}
|
|
}
|