From 621d9e5c413e561293d7484b93882d985b3fe15f Mon Sep 17 00:00:00 2001 From: Endi Sukma Dewata Date: Sat, 24 Mar 2012 02:27:47 -0500 Subject: Removed unnecessary pki folder. Previously the source code was located inside a pki folder. This folder was created during svn migration and is no longer needed. This folder has now been removed and the contents have been moved up one level. Ticket #131 --- .../cmscore/ldapconn/LdapAnonConnFactory.java | 467 ++++++++++++++++++ .../cmscore/ldapconn/LdapAnonConnection.java | 92 ++++ .../netscape/cmscore/ldapconn/LdapAuthInfo.java | 298 ++++++++++++ .../cmscore/ldapconn/LdapBoundConnFactory.java | 529 +++++++++++++++++++++ .../cmscore/ldapconn/LdapBoundConnection.java | 220 +++++++++ .../netscape/cmscore/ldapconn/LdapConnInfo.java | 119 +++++ .../cmscore/ldapconn/LdapJssSSLSocketFactory.java | 109 +++++ 7 files changed, 1834 insertions(+) create mode 100644 base/common/src/com/netscape/cmscore/ldapconn/LdapAnonConnFactory.java create mode 100644 base/common/src/com/netscape/cmscore/ldapconn/LdapAnonConnection.java create mode 100644 base/common/src/com/netscape/cmscore/ldapconn/LdapAuthInfo.java create mode 100644 base/common/src/com/netscape/cmscore/ldapconn/LdapBoundConnFactory.java create mode 100644 base/common/src/com/netscape/cmscore/ldapconn/LdapBoundConnection.java create mode 100644 base/common/src/com/netscape/cmscore/ldapconn/LdapConnInfo.java create mode 100644 base/common/src/com/netscape/cmscore/ldapconn/LdapJssSSLSocketFactory.java (limited to 'base/common/src/com/netscape/cmscore/ldapconn') diff --git a/base/common/src/com/netscape/cmscore/ldapconn/LdapAnonConnFactory.java b/base/common/src/com/netscape/cmscore/ldapconn/LdapAnonConnFactory.java new file mode 100644 index 000000000..dc4c86547 --- /dev/null +++ b/base/common/src/com/netscape/cmscore/ldapconn/LdapAnonConnFactory.java @@ -0,0 +1,467 @@ +// --- BEGIN COPYRIGHT BLOCK --- +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; version 2 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// (C) 2007 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- +package com.netscape.cmscore.ldapconn; + +import netscape.ldap.LDAPConnection; +import netscape.ldap.LDAPException; +import netscape.ldap.LDAPSocketFactory; + +import com.netscape.certsrv.apps.CMS; +import com.netscape.certsrv.base.EBaseException; +import com.netscape.certsrv.base.IConfigStore; +import com.netscape.certsrv.ldap.ELdapException; +import com.netscape.certsrv.ldap.ELdapServerDownException; +import com.netscape.certsrv.ldap.ILdapConnFactory; +import com.netscape.certsrv.logging.ILogger; + +/** + * Factory for getting LDAP Connections to a LDAP server + * each connection is a seperate thread that can be bound to a different + * authentication dn and password. + */ +public class LdapAnonConnFactory implements ILdapConnFactory { + protected int mMinConns = 5; + protected int mMaxConns = 1000; + protected LdapConnInfo mConnInfo = null; + + private ILogger mLogger = CMS.getLogger(); + + public static final String PROP_MINCONNS = "minConns"; + public static final String PROP_MAXCONNS = "maxConns"; + public static final String PROP_LDAPCONNINFO = "ldapconn"; + + public static final String PROP_ERROR_IF_DOWN = "errorIfDown"; + + private int mNumConns = 0; // number of available conns in array + private int mTotal = 0; // total num conns + private AnonConnection mConns[] = null; + + private boolean mInited = false; + + private boolean mErrorIfDown; + private boolean mDefErrorIfDown = false; + + /** + * Constructor for initializing from the config store. + * must be followed by init(IConfigStore) + */ + public LdapAnonConnFactory() { + } + + public LdapAnonConnFactory(boolean defErrorIfDown) { + mDefErrorIfDown = defErrorIfDown; + } + + /** + * Constructor for LdapAnonConnFactory + * + * @param minConns minimum number of connections to have available + * @param maxConns max number of connections to have available. This is + * the maximum number of clones of this connection one wants to allow. + * @param serverInfo server connection info - host, port, etc. + */ + public LdapAnonConnFactory(int minConns, int maxConns, + LdapConnInfo connInfo) throws ELdapException { + init(minConns, maxConns, connInfo); + } + + public int totalConn() { + return mTotal; + } + + public int freeConn() { + return mNumConns; + } + + public int maxConn() { + return mMaxConns; + } + + /** + * init routine to be called when initialize from config store. + */ + public void init(IConfigStore config) throws EBaseException, ELdapException { + String minStr = config.getString(PROP_MINCONNS, ""); + String maxStr = config.getString(PROP_MAXCONNS, ""); + int minConns = mMinConns; + int maxConns = mMaxConns; + + // if it is "", use the default value + if (!minStr.equals("")) { + try { + minConns = Integer.parseInt(minStr); + } catch (NumberFormatException e) { + log(ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_LDAPCONN_MIN_CONN")); + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_NUMBER_FORMAT_1", PROP_MINCONNS)); + } + } + + // if it is "", use the default value + if (!maxStr.equals("")) { + try { + maxConns = Integer.parseInt(maxStr); + } catch (NumberFormatException e) { + log(ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_LDAPCONN_MAX_CONN")); + throw new EBaseException(CMS.getUserMessage("CMS_BASE_INVALID_NUMBER_FORMAT_1", PROP_MAXCONNS)); + } + } + + mErrorIfDown = config.getBoolean(PROP_ERROR_IF_DOWN, mDefErrorIfDown); + + init(minConns, maxConns, + new LdapConnInfo(config.getSubStore(PROP_LDAPCONNINFO))); + } + + /** + * initialize routine from parameters. + */ + protected void init(int minConns, int maxConns, LdapConnInfo connInfo) + throws ELdapException { + if (mInited) + return; // XXX should throw exception here ? + + if (minConns <= 0 || maxConns <= 0 || minConns > maxConns) + throw new ELdapException( + CMS.getUserMessage("CMS_LDAP_INVALID_NUMCONN_PARAMETERS")); + if (connInfo == null) + throw new IllegalArgumentException("connInfo is Null!"); + + mMinConns = minConns; + mMaxConns = maxConns; + mConnInfo = connInfo; + + mConns = new AnonConnection[mMaxConns]; + + log(ILogger.LL_INFO, + "Created: min " + minConns + " max " + maxConns + + " host " + connInfo.getHost() + " port " + connInfo.getPort() + + " secure " + connInfo.getSecure()); + + // initalize minimum number of connection handles available. + makeMinimum(mErrorIfDown); + mInited = true; + } + + /** + * make the mininum configured connections + */ + protected void makeMinimum(boolean errorIfDown) throws ELdapException { + try { + if (mNumConns < mMinConns && mTotal < mMaxConns) { + int increment = Math.min(mMinConns - mNumConns, mMaxConns - mTotal); + + CMS.debug( + "increasing minimum number of connections by " + increment); + for (int i = increment - 1; i >= 0; i--) { + mConns[i] = new AnonConnection(mConnInfo); + } + mTotal += increment; + mNumConns += increment; + CMS.debug( + "new total number of connections " + mTotal); + CMS.debug( + "new total available connections " + mNumConns); + } + } catch (LDAPException e) { + // XXX errorCodeToString() used here so users won't see message. + // though why are messages from exceptions being displayed to + // users ? + if (e.getLDAPResultCode() == LDAPException.UNAVAILABLE) { + // need to intercept this because message from LDAP is + // "DSA is unavailable" which confuses with DSA PKI. + log(ILogger.LL_FAILURE, + "Cannot connect to Ldap server. Error: " + + "Ldap Server host " + mConnInfo.getHost() + + " int " + mConnInfo.getPort() + " is unavailable."); + if (errorIfDown) { + throw new ELdapServerDownException( + CMS.getUserMessage("CMS_LDAP_SERVER_UNAVAILABLE", + mConnInfo.getHost(), "" + mConnInfo.getPort())); + } + } else { + log(ILogger.LL_FAILURE, + "Cannot connect to ldap server. error: " + e.toString()); + String errmsg = e.errorCodeToString(); + + if (errmsg == null) + errmsg = e.toString(); + throw new ELdapException( + CMS.getUserMessage("CMS_LDAP_CONNECT_TO_LDAP_SERVER_FAILED", + mConnInfo.getHost(), "" + (Integer.valueOf(mConnInfo.getPort())), errmsg)); + } + } + } + + /** + * Gets connection from this factory. + * All connections gotten from this factory must be returned. + * If not the max number of connections may be reached prematurely. + * The best thing to put returnConn in a finally clause so it + * always gets called. For example, + * + *
+     * LDAPConnection c = null;
+     * try {
+     *     c = factory.getConn();
+     *     myclass.do_something_with_c(c);
+     * } catch (ELdapException e) {
+     *     handle_error_here();
+     * } finally {
+     *     factory.returnConn(c);
+     * }
+     * 
+ */ + public LDAPConnection getConn() + throws ELdapException { + return getConn(true); + } + + /** + * Returns a LDAP connection - a clone of the master connection. + * All connections should be returned to the factory using returnConn() + * to recycle connection objects. + * If not returned the limited max number is affected but if that + * number is large not much harm is done. + * Returns null if maximum number of connections reached. + *

+ * The best thing to put returnConn in a finally clause so it always gets called. For example, + * + *

+     * LDAPConnection c = null;
+     * try {
+     *     c = factory.getConn();
+     *     myclass.do_something_with_c(c);
+     * } catch (ELdapException e) {
+     *     handle_error_here();
+     * } finally {
+     *     factory.returnConn(c);
+     * }
+     * 
+ */ + public synchronized LDAPConnection getConn(boolean waitForConn) + throws ELdapException { + boolean waited = false; + + CMS.debug("LdapAnonConnFactory::getConn"); + if (mNumConns == 0) + makeMinimum(true); + if (mNumConns == 0) { + if (!waitForConn) + return null; + try { + CMS.debug("getConn(): out of ldap connections"); + log(ILogger.LL_WARN, + "Ran out of ldap connections available " + + "in ldap connection pool to " + + mConnInfo.getHost() + ":" + mConnInfo.getPort() + ". " + + "This could be a temporary condition or an indication of " + + "something more serious that can cause the server to " + + "hang."); + waited = true; + while (mNumConns == 0) { + wait(); + } + } catch (InterruptedException e) { + } + } + + mNumConns--; + AnonConnection conn = mConns[mNumConns]; + + mConns[mNumConns] = null; + if (waited) { + log(ILogger.LL_WARN, + "Ldap connections are available again in ldap connection pool " + + "to " + mConnInfo.getHost() + ":" + mConnInfo.getPort()); + } + CMS.debug("LdapAnonConnFactory.getConn(): num avail conns now " + mNumConns); + //Beginning of fix for Bugzilla #630176 + boolean isConnected = false; + if (conn != null) { + isConnected = conn.isConnected(); + } + + if (!isConnected) { + CMS.debug("LdapAnonConnFactory.getConn(): selected conn is down, try to reconnect..."); + conn = null; + try { + conn = new AnonConnection(mConnInfo); + } catch (LDAPException e) { + CMS.debug("LdapAnonConnFactory.getConn(): error when trying to bring back a down connection."); + throw new ELdapException( + CMS.getUserMessage("CMS_LDAP_CONNECT_TO_LDAP_SERVER_FAILED", + mConnInfo.getHost(), "" + (Integer.valueOf(mConnInfo.getPort())), e.toString())); + } + } + //This is the end of the fix for Bugzilla #630176 + + return conn; + } + + /** + * Returns a connection to the factory for recycling. + * All connections gotten from this factory must be returned. + * If not the max number of connections may be reached prematurely. + *

+ * The best thing to put returnConn in a finally clause so it always gets called. For example, + * + *

+     * LDAPConnection c = null;
+     * try {
+     *     c = factory.getConn();
+     *     myclass.do_something_with_c(c);
+     * } catch (ELdapException e) {
+     *     handle_error_here();
+     * } finally {
+     *     factory.returnConn(c);
+     * }
+     * 
+ */ + public synchronized void returnConn(LDAPConnection conn) { + if (conn == null) { + return; + } + // check if conn is valid and from this factory. + AnonConnection anon = (AnonConnection) conn; + + if (anon.getFacId() != mConns) { + // returning a connection not from this factory. + log(ILogger.LL_WARN, "returnConn: unknown connection."); + } + // check if conn has already been returned. + for (int i = 0; i < mNumConns; i++) { + // returning connection already returned. + if (mConns[i] == anon) { + + /* swallow this error but see who's doing it. */ + log(ILogger.LL_WARN, + "returnConn: previously returned connection."); + } + } + + // this returned connection might authenticate as someone other than + // anonymonus. Reset it to anonymous first before it returns + // to the pool. + try { + anon.authenticate(null, null); + + // return conn. + CMS.debug("returnConn: mNumConns now " + mNumConns); + } catch (LDAPException e) { + log(ILogger.LL_WARN, + "Could not re-authenticate ldap connection to anonymous." + + " Error " + e); + } + // return the connection even if can't reauthentication anon. + // most likely server was down. + mConns[mNumConns++] = anon; + + notify(); + } + + protected void finalize() + throws Exception { + reset(); + } + + /** + * returns connection info. + */ + public LdapConnInfo getConnInfo() { + return mConnInfo; + } + + /** + * resets this factory - if no connections outstanding, + * disconnections all connections and resets everything to 0 as if + * no connections were ever made. intended to be called just before + * shutdown or exit to disconnection & cleanup connections. + */ + // ok only if no connections outstanding. + public synchronized void reset() + throws ELdapException { + if (mNumConns == mTotal) { + for (int i = 0; i < mNumConns; i++) { + try { + CMS.debug("disconnecting connection " + i); + mConns[i].disconnect(); + } catch (LDAPException e) { + log(ILogger.LL_INFO, + "exception during disconnect: " + e.toString()); + } + mConns[i] = null; + } + mTotal = 0; + mNumConns = 0; + } else { + log(ILogger.LL_INFO, + "Cannot reset() while connections not all returned"); + throw new ELdapException( + CMS.getUserMessage("CMS_LDAP_CANNOT_RESET_CONNFAC")); + } + } + + /** + * handy routine for logging in this class. + */ + private void log(int level, String msg) { + mLogger.log(ILogger.EV_SYSTEM, ILogger.S_LDAP, level, + "In Ldap (anonymous) connection pool to" + + " host " + mConnInfo.getHost() + + " port " + mConnInfo.getPort() + ", " + msg); + } + + /** + * used to keep track of connections from this factory. + */ + public class AnonConnection extends LdapAnonConnection { + /** + * + */ + private static final long serialVersionUID = 4813780131074412404L; + + public AnonConnection(LdapConnInfo connInfo) + throws LDAPException { + super(connInfo); + } + + public AnonConnection(String host, int port, int version, + LDAPSocketFactory fac) + throws LDAPException { + super(host, port, version, fac); + } + + /** + * instantiates a non-secure connection to a ldap server + */ + public AnonConnection(String host, int port, int version) + throws LDAPException { + super(host, port, version); + } + + /** + * used only to identify the factory from which this came. + * mConns to identify factory. + */ + public AnonConnection[] getFacId() { + return mConns; + } + } +} diff --git a/base/common/src/com/netscape/cmscore/ldapconn/LdapAnonConnection.java b/base/common/src/com/netscape/cmscore/ldapconn/LdapAnonConnection.java new file mode 100644 index 000000000..1dc9723a8 --- /dev/null +++ b/base/common/src/com/netscape/cmscore/ldapconn/LdapAnonConnection.java @@ -0,0 +1,92 @@ +// --- BEGIN COPYRIGHT BLOCK --- +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; version 2 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// (C) 2007 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- +package com.netscape.cmscore.ldapconn; + +import netscape.ldap.LDAPConnection; +import netscape.ldap.LDAPException; +import netscape.ldap.LDAPSocketFactory; +import netscape.ldap.LDAPv2; + +/** + * A LDAP connection that is bound to a server host, port and secure type. + * Makes a LDAP connection when instantiated. + * Cannot establish another LDAP connection after construction. + * LDAPConnection connect methods are overridden to prevent this. + */ +public class LdapAnonConnection extends LDAPConnection { + + /** + * + */ + private static final long serialVersionUID = 6671180208419384682L; + + /** + * instantiates a connection to a ldap server + */ + public LdapAnonConnection(LdapConnInfo connInfo) + throws LDAPException { + super(connInfo.getSecure() ? new LdapJssSSLSocketFactory() : null); + + // Set option to automatically follow referrals. + // rebind info is also anonymous. + boolean followReferrals = connInfo.getFollowReferrals(); + + setOption(LDAPv2.REFERRALS, new Boolean(followReferrals)); + + super.connect(connInfo.getVersion(), + connInfo.getHost(), connInfo.getPort(), null, null); + } + + /** + * instantiates a connection to a ldap server + */ + public LdapAnonConnection(String host, int port, int version, + LDAPSocketFactory fac) + throws LDAPException { + super(fac); + super.connect(version, host, port, null, null); + } + + /** + * instantiates a non-secure connection to a ldap server + */ + public LdapAnonConnection(String host, int port, int version) + throws LDAPException { + super(); + super.connect(version, host, port, null, null); + } + + /** + * overrides superclass connect. + * does not allow reconnect. + */ + public void connect(String host, int port) throws LDAPException { + throw new RuntimeException( + "this LdapAnonConnection already connected: connect(h,p)"); + } + + /** + * overrides superclass connect. + * does not allow reconnect. + */ + public void connect(int version, String host, int port, + String dn, String pw) throws LDAPException { + throw new RuntimeException( + "this LdapAnonConnection already connected: connect(v,h,p)"); + } +} diff --git a/base/common/src/com/netscape/cmscore/ldapconn/LdapAuthInfo.java b/base/common/src/com/netscape/cmscore/ldapconn/LdapAuthInfo.java new file mode 100644 index 000000000..b1af367b9 --- /dev/null +++ b/base/common/src/com/netscape/cmscore/ldapconn/LdapAuthInfo.java @@ -0,0 +1,298 @@ +// --- BEGIN COPYRIGHT BLOCK --- +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; version 2 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// (C) 2007 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- +package com.netscape.cmscore.ldapconn; + +import java.util.Hashtable; + +import netscape.ldap.LDAPConnection; +import netscape.ldap.LDAPException; + +import com.netscape.certsrv.apps.CMS; +import com.netscape.certsrv.base.EBaseException; +import com.netscape.certsrv.base.IConfigStore; +import com.netscape.certsrv.ldap.ILdapAuthInfo; +import com.netscape.cmsutil.password.IPasswordStore; + +/** + * class for reading ldap authentication info from config store + */ +public class LdapAuthInfo implements ILdapAuthInfo { + + protected int mType = -1; + protected String[] mParms = null; + + private boolean mInited = false; + + private static Hashtable passwords = new Hashtable(); + + /** + * must call init(config) after this constructor. + */ + public LdapAuthInfo() { + } + + /** + * constructs ldap auth info directly from config store. + */ + public LdapAuthInfo(IConfigStore config) throws EBaseException { + init(config); + } + + /** + * constructs ldap auth info directly from config store, and verifies + * the password by attempting to connect to the server. + */ + public LdapAuthInfo(IConfigStore config, String host, int port, boolean secure) + throws EBaseException { + init(config, host, port, secure); + } + + public String getPasswordFromStore(String prompt) { + String pwd = null; + CMS.debug("LdapAuthInfo: getPasswordFromStore: try to get it from password store"); + + // hey - should use password store interface to allow different implementations + // but the problem is, other parts of the system just go directly to the file + // so calling CMS.getPasswordStore() will give you an outdated one + /* + IConfigStore mainConfig = CMS.getConfigStore(); + String pwdFile = mainConfig.getString("passwordFile"); + FileConfigStore pstore = new FileConfigStore(pwdFile); + */ + IPasswordStore pwdStore = CMS.getPasswordStore(); + CMS.debug("LdapAuthInfo: getPasswordFromStore: about to get from passwored store: " + prompt); + + // support publishing dirsrv with different pwd than internaldb + + // Finally, interactively obtain the password from the user + if (pwdStore != null) { + CMS.debug("LdapAuthInfo: getPasswordFromStore: password store available"); + pwd = pwdStore.getPassword(prompt); + // pwd = pstore.getString(prompt); + if (pwd == null) { + CMS.debug("LdapAuthInfo: getPasswordFromStore: password for " + prompt + + " not found, trying internaldb"); + + // pwd = pstore.getString("internaldb"); + + pwd = pwdStore.getPassword("internaldb"); // last resort + } else + CMS.debug("LdapAuthInfo: getPasswordFromStore: password found for prompt in password store"); + } else + CMS.debug("LdapAuthInfo: getPasswordFromStore: password store not available: pwdStore is null"); + + return pwd; + } + + /** + * initialize this class from the config store. + */ + public void init(IConfigStore config) throws EBaseException { + init(config, null, 0, true); + } + + /** + * initialize this class from the config store, and verify the password. + * + * @param host The host that the directory server is running on. + * This will be used to verify the password by attempting to connect. + * If it is null, the password will not be verified. + * @param port The port that the directory server is running on. + */ + public void init(IConfigStore config, String host, int port, boolean secure) + throws EBaseException { + + CMS.debug("LdapAuthInfo: init()"); + if (mInited) { + CMS.debug("LdapAuthInfo: already initialized"); + return; // XXX throw exception here ? + } + CMS.debug("LdapAuthInfo: init begins"); + + String authTypeStr = config.getString(PROP_LDAPAUTHTYPE); + + if (authTypeStr.equals(LDAP_BASICAUTH_STR)) { + // is the password found in memory? + boolean inMem = false; + mType = LDAP_AUTHTYPE_BASICAUTH; + mParms = new String[2]; + mParms[0] = config.getString(PROP_BINDDN); + + // Passwords should only be written to the file for testing, + // never in production + mParms[1] = config.getString(PROP_BINDPW, null); + + // Next, see if this password has been requested before + String prompt = config.getString(PROP_BINDPW_PROMPT, null); + + if (prompt == null) { + prompt = "LDAP Authentication"; + CMS.debug("LdapAuthInfo: init: prompt is null, change to " + prompt); + } else + CMS.debug("LdapAuthInfo: init: prompt is " + prompt); + + if (mParms[1] == null) { + CMS.debug("LdapAuthInfo: init: try getting from memory cache"); + mParms[1] = passwords.get(prompt); + if (mParms[1] != null) { + inMem = true; + CMS.debug("LdapAuthInfo: init: got password from memory"); + } else + CMS.debug("LdapAuthInfo: init: password not in memory"); + } else + CMS.debug("LdapAuthInfo: init: found password from config"); + + if (mParms[1] == null) { + mParms[1] = getPasswordFromStore(prompt); + } else { + CMS.debug("LdapAuthInfo: init: password found for prompt."); + } + + // verify the password + if ((mParms[1] != null) && (!mParms[1].equals("")) && (host == null || + authInfoOK(host, port, secure, mParms[0], mParms[1]))) { + // The password is OK or uncheckable + CMS.debug("LdapAuthInfo: password ok: store in memory cache"); + passwords.put(prompt, mParms[1]); + } else { + if (mParms[1] == null) + CMS.debug("LdapAuthInfo: password not found"); + else { + CMS.debug("LdapAuthInfo: password does not work"); + /* what do you know? Our IPasswordStore does not have a remove function. + pstore.remove("internaldb"); + */ + if (inMem) { + // this is for the case when admin changes pwd + // from console + mParms[1] = getPasswordFromStore(prompt); + if (authInfoOK(host, port, secure, mParms[0], mParms[1])) { + CMS.debug("LdapAuthInfo: password ok: store in memory cache"); + passwords.put(prompt, mParms[1]); + } + } + } + } + + } else if (authTypeStr.equals(LDAP_SSLCLIENTAUTH_STR)) { + mType = LDAP_AUTHTYPE_SSLCLIENTAUTH; + mParms = new String[1]; + mParms[0] = config.getString(PROP_CLIENTCERTNICKNAME, null); + } else { + throw new IllegalArgumentException( + "Unknown Ldap authentication type " + authTypeStr); + } + mInited = true; + CMS.debug("LdapAuthInfo: init ends"); + } + + public void reset() { + try { + conn.disconnect(); + } catch (LDAPException e) { + } + } + + /** + * Verifies the distinguished name and password by attempting to + * authenticate to the server. If we connect to the server but cannot + * authenticate, we conclude that the DN or password is invalid. If + * we cannot connect at all, we don't know, so we return true + * (there's no sense asking for the password again since we can't verify + * it anyway). If we connect and authenticate successfully, we know + * the DN and password are correct, so we return true. + */ + private static LDAPConnection conn = new LDAPConnection(); + + private static boolean + authInfoOK(String host, int port, boolean secure, String dn, String pw) { + + // We dont perform auth checking if we are in SSL mode. + if (secure) + return true; + + boolean connected = false, authenticated = false; + + try { + conn.connect(host, port); + connected = true; + conn.authenticate(dn, pw); + authenticated = true; + } catch (LDAPException e) { + } + + /** + * There is a bug in LDAP SDK. VM will crash on NT if + * we connect and disconnect too many times. + **/ + + /** + * if( connected ) { + * try { + * conn.disconnect(); + * } catch( LDAPException e ) { } + * } + **/ + + if (connected && !authenticated) { + return false; + } else { + return true; + } + } + + /** + * get authentication type. + * + * @return one of:
+ * LdapAuthInfo.LDAP_AUTHTYPE_BASICAUTH or + * LdapAuthInfo.LDAP_AUTHTYPE_SSLCLIENTAUTH + */ + public int getAuthType() { + return mType; + } + + /** + * get params for authentication + * + * @return array of parameters for this authentication. + */ + public String[] getParms() { + return (String[]) mParms.clone(); + } + + /** + * add password + */ + public void addPassword(String prompt, String pw) { + try { + passwords.put(prompt, pw); + } catch (Exception e) { + } + } + + /** + * remove password + */ + public void removePassword(String prompt) { + try { + passwords.remove(prompt); + } catch (Exception e) { + } + } +} diff --git a/base/common/src/com/netscape/cmscore/ldapconn/LdapBoundConnFactory.java b/base/common/src/com/netscape/cmscore/ldapconn/LdapBoundConnFactory.java new file mode 100644 index 000000000..b4839f7d5 --- /dev/null +++ b/base/common/src/com/netscape/cmscore/ldapconn/LdapBoundConnFactory.java @@ -0,0 +1,529 @@ +// --- BEGIN COPYRIGHT BLOCK --- +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; version 2 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// (C) 2007 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- +package com.netscape.cmscore.ldapconn; + +import netscape.ldap.LDAPConnection; +import netscape.ldap.LDAPException; +import netscape.ldap.LDAPSocketFactory; + +import com.netscape.certsrv.apps.CMS; +import com.netscape.certsrv.base.EBaseException; +import com.netscape.certsrv.base.IConfigStore; +import com.netscape.certsrv.ldap.ELdapException; +import com.netscape.certsrv.ldap.ELdapServerDownException; +import com.netscape.certsrv.ldap.ILdapBoundConnFactory; +import com.netscape.certsrv.logging.ILogger; + +/** + * Factory for getting LDAP Connections to a LDAP server with the same + * LDAP authentication. + * XXX not sure how useful this is given that LDAPConnection itself can + * be shared by multiple threads and cloned. + */ +public class LdapBoundConnFactory implements ILdapBoundConnFactory { + protected int mMinConns = 5; + protected int mMaxConns = 1000; + protected LdapConnInfo mConnInfo = null; + protected LdapAuthInfo mAuthInfo = null; + + private ILogger mLogger = CMS.getLogger(); + + public static final String PROP_MINCONNS = "minConns"; + public static final String PROP_MAXCONNS = "maxConns"; + public static final String PROP_LDAPCONNINFO = "ldapconn"; + public static final String PROP_LDAPAUTHINFO = "ldapauth"; + + public static final String PROP_ERROR_IF_DOWN = "errorIfDown"; + + private int mNumConns = 0; // number of available conns in array + private int mTotal = 0; // total num conns + + private boolean doCloning = true; + private LdapBoundConnection mMasterConn = null; // master connection object. + private BoundConnection mConns[]; + + /** + * return error if server is down at creation time. + */ + private boolean mErrorIfDown; + + /** + * default value for the above at init time. + */ + private boolean mDefErrorIfDown = false; + + /** + * Constructor for initializing from the config store. + * must be followed by init(IConfigStore) + */ + public LdapBoundConnFactory() { + } + + public LdapBoundConnFactory(boolean defErrorIfDown) { + mDefErrorIfDown = defErrorIfDown; + } + + public int totalConn() { + return mTotal; + } + + public int freeConn() { + return mNumConns; + } + + public int maxConn() { + return mMaxConns; + } + + /** + * Constructor for LdapBoundConnFactory + * + * @param minConns minimum number of connections to have available + * @param maxConns max number of connections to have available. This is + * the maximum number of clones of this connection or separate connections one wants to allow. + * @param serverInfo server connection info - host, port, etc. + */ + public LdapBoundConnFactory(int minConns, int maxConns, + LdapConnInfo connInfo, LdapAuthInfo authInfo) throws ELdapException { + init(minConns, maxConns, connInfo, authInfo); + } + + /** + * Constructor for initialize + */ + public void init(IConfigStore config) + throws ELdapException, EBaseException { + + CMS.debug("LdapBoundConnFactory: init "); + LdapConnInfo connInfo = + new LdapConnInfo(config.getSubStore(PROP_LDAPCONNINFO)); + + mErrorIfDown = config.getBoolean(PROP_ERROR_IF_DOWN, mDefErrorIfDown); + + doCloning = config.getBoolean("doCloning", true); + + CMS.debug("LdapBoundConnFactory:doCloning " + doCloning); + init(config.getInteger(PROP_MINCONNS, mMinConns), + config.getInteger(PROP_MAXCONNS, mMaxConns), + connInfo, + new LdapAuthInfo(config.getSubStore(PROP_LDAPAUTHINFO), + connInfo.getHost(), connInfo.getPort(), connInfo.getSecure())); + } + + /** + * initialize parameters obtained from either constructor or + * config store + * + * @param minConns minimum number of connection handls to have available. + * @param maxConns maximum total number of connections to ever have. + * @param connInfo ldap connection info. + * @param authInfo ldap authentication info. + * @exception ELdapException if any error occurs. + */ + private void init(int minConns, int maxConns, + LdapConnInfo connInfo, LdapAuthInfo authInfo) + throws ELdapException { + if (minConns <= 0 || maxConns <= 0 || minConns > maxConns) + throw new ELdapException( + CMS.getUserMessage("CMS_LDAP_INVALID_NUMCONN_PARAMETERS")); + if (connInfo == null || authInfo == null) + throw new IllegalArgumentException("connInfo or authInfo is null!"); + + mMinConns = minConns; + mMaxConns = maxConns; + mConnInfo = connInfo; + mAuthInfo = authInfo; + + mConns = new BoundConnection[mMaxConns]; + + // Create connection handle and make initial connection + CMS.debug( + "init: before makeConnection errorIfDown is " + mErrorIfDown); + makeConnection(mErrorIfDown); + + CMS.debug( + "initializing with mininum " + mMinConns + " and maximum " + mMaxConns + + " connections to " + + "host " + mConnInfo.getHost() + " port " + mConnInfo.getPort() + + ", secure connection, " + mConnInfo.getSecure() + + ", authentication type " + mAuthInfo.getAuthType()); + + // initalize minimum number of connection handles available. + makeMinimum(); + } + + /** + * makes the initial master connection used to clone others.. + * + * @exception ELdapException if any error occurs. + */ + protected void makeConnection(boolean errorIfDown) throws ELdapException { + CMS.debug("makeConnection: errorIfDown " + errorIfDown); + try { + mMasterConn = new BoundConnection(mConnInfo, mAuthInfo); + } catch (LDAPException e) { + if (e.getLDAPResultCode() == LDAPException.UNAVAILABLE) { + // need to intercept this because message from LDAP is + // "DSA is unavailable" which confuses with DSA PKI. + log(ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_LDAPCONN_CONNECT_SERVER", + mConnInfo.getHost(), + Integer.toString(mConnInfo.getPort()))); + if (errorIfDown) { + throw new ELdapServerDownException( + CMS.getUserMessage("CMS_LDAP_SERVER_UNAVAILABLE", + mConnInfo.getHost(), "" + mConnInfo.getPort())); + } + } else { + log(ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_LDAPCONN_FAILED_SERVER", e.toString())); + throw new ELdapException( + CMS.getUserMessage("CMS_LDAP_CONNECT_TO_LDAP_SERVER_FAILED", + mConnInfo.getHost(), "" + (Integer.valueOf(mConnInfo.getPort())), e.toString())); + } + } + } + + /** + * makes subsequent connections if cloning is not used . + * + * @exception ELdapException if any error occurs. + */ + private LdapBoundConnection makeNewConnection(boolean errorIfDown) throws ELdapException { + CMS.debug("LdapBoundConnFactory:In makeNewConnection: errorIfDown " + errorIfDown); + LdapBoundConnection conn = null; + try { + conn = new BoundConnection(mConnInfo, mAuthInfo); + } catch (LDAPException e) { + if (e.getLDAPResultCode() == LDAPException.UNAVAILABLE) { + // need to intercept this because message from LDAP is + // "DSA is unavailable" which confuses with DSA PKI. + log(ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_LDAPCONN_CONNECT_SERVER", + mConnInfo.getHost(), + Integer.toString(mConnInfo.getPort()))); + if (errorIfDown) { + throw new ELdapServerDownException( + CMS.getUserMessage("CMS_LDAP_SERVER_UNAVAILABLE", + mConnInfo.getHost(), "" + mConnInfo.getPort())); + } + } else { + log(ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_LDAPCONN_FAILED_SERVER", e.toString())); + throw new ELdapException( + CMS.getUserMessage("CMS_LDAP_CONNECT_TO_LDAP_SERVER_FAILED", + mConnInfo.getHost(), "" + (Integer.valueOf(mConnInfo.getPort())), e.toString())); + } + } + + return conn; + } + + /** + * makes the minumum number of connections + */ + private void makeMinimum() throws ELdapException { + if (mMasterConn == null || mMasterConn.isConnected() == false) + return; + int increment; + + if (mNumConns < mMinConns && mTotal <= mMaxConns) { + increment = Math.min(mMinConns - mNumConns, mMaxConns - mTotal); + CMS.debug( + "increasing minimum connections by " + increment); + for (int i = increment - 1; i >= 0; i--) { + + if (doCloning == true) { + mConns[i] = (BoundConnection) mMasterConn.clone(); + } else { + mConns[i] = (BoundConnection) makeNewConnection(true); + } + + } + mTotal += increment; + mNumConns += increment; + CMS.debug("new total available connections " + mTotal); + CMS.debug("new number of connections " + mNumConns); + } + } + + /** + * gets a conenction from this factory. + * All connections obtained from the factory must be returned by + * returnConn() method. + * The best thing to do is to put returnConn in a finally clause so it + * always gets called. For example, + * + *
+     * LDAPConnection c = null;
+     * try {
+     *     c = factory.getConn();
+     *     myclass.do_something_with_c(c);
+     * } catch (ELdapException e) {
+     *     handle_error_here();
+     * } finally {
+     *     factory.returnConn(c);
+     * }
+     * 
+ */ + public LDAPConnection getConn() + throws ELdapException { + return getConn(true); + } + + /** + * Returns a LDAP connection - a clone of the master connection. + * All connections should be returned to the factory using returnConn() + * to recycle connection objects. + * If not returned the limited max number is affected but if that + * number is large not much harm is done. + * Returns null if maximum number of connections reached. + * The best thing to do is to put returnConn in a finally clause so it + * always gets called. For example, + * + *
+     * LDAPConnection c = null;
+     * try {
+     *     c = factory.getConn();
+     *     myclass.do_something_with_c(c);
+     * } catch (ELdapException e) {
+     *     handle_error_here();
+     * } finally {
+     *     factory.returnConn(c);
+     * }
+     * 
+ */ + public synchronized LDAPConnection getConn(boolean waitForConn) + throws ELdapException { + boolean waited = false; + + CMS.debug("In LdapBoundConnFactory::getConn()"); + if (mMasterConn != null) + CMS.debug("masterConn is connected: " + mMasterConn.isConnected()); + else + CMS.debug("masterConn is null."); + + if (mMasterConn == null || !mMasterConn.isConnected()) { + try { + makeConnection(true); + } catch (ELdapException e) { + mMasterConn = null; + CMS.debug("Can't create master connection in LdapBoundConnFactory::getConn! " + e.toString()); + throw e; + } + } + + if (mNumConns == 0) + makeMinimum(); + if (mNumConns == 0) { + if (!waitForConn) + return null; + try { + CMS.debug("getConn: out of ldap connections"); + log(ILogger.LL_WARN, + "Ran out of ldap connections available " + + "in ldap connection pool to " + + mConnInfo.getHost() + ":" + mConnInfo.getPort() + ". " + + "This could be a temporary condition or an indication of " + + "something more serious that can cause the server to " + + "hang."); + waited = true; + while (mNumConns == 0) + wait(); + } catch (InterruptedException e) { + } + } + mNumConns--; + LDAPConnection conn = mConns[mNumConns]; + + boolean isConnected = false; + if (conn != null) { + isConnected = conn.isConnected(); + } + + CMS.debug("getConn: conn is connected " + isConnected); + + //If masterConn is still alive, lets try to bring this one + //back to life + + if ((isConnected == false) && (mMasterConn != null) + && (mMasterConn.isConnected() == true)) { + CMS.debug("Attempt to bring back down connection."); + + if (doCloning == true) { + mConns[mNumConns] = (BoundConnection) mMasterConn.clone(); + } else { + try { + mConns[mNumConns] = (BoundConnection) makeNewConnection(true); + } catch (ELdapException e) { + mConns[mNumConns] = null; + } + } + conn = mConns[mNumConns]; + + CMS.debug("Re-animated connection: " + conn); + } + + mConns[mNumConns] = null; + + if (waited) { + log(ILogger.LL_WARN, + "Ldap connections are available again in ldap connection pool " + + "to " + mConnInfo.getHost() + ":" + mConnInfo.getPort()); + } + CMS.debug("getConn: mNumConns now " + mNumConns); + + return conn; + } + + /** + * Teturn connection to the factory. + * This is mandatory after a getConn(). + * The best thing to do is to put returnConn in a finally clause so it + * always gets called. For example, + * + *
+     * LDAPConnection c = null;
+     * try {
+     *     c = factory.getConn();
+     *     myclass.do_something_with_c(c);
+     * } catch (ELdapException e) {
+     *     handle_error_here();
+     * } finally {
+     *     factory.returnConn(c);
+     * }
+     * 
+ */ + public synchronized void returnConn(LDAPConnection conn) { + if (conn == null) { + return; + } + BoundConnection boundconn = (BoundConnection) conn; + + if (boundconn.getFacId() != mConns) { + log(ILogger.LL_WARN, "returnConn: unknown connection."); + } + for (int i = 0; i < mNumConns; i++) { + if (mConns[i] == conn) { + CMS.debug( + "returnConn: previously returned connection."); + } + } + mConns[mNumConns++] = boundconn; + CMS.debug("returnConn: mNumConns now " + mNumConns); + notify(); + } + + /** + * handy routine for logging in this class. + */ + private void log(int level, String msg) { + mLogger.log(ILogger.EV_SYSTEM, ILogger.S_LDAP, level, + "In Ldap (bound) connection pool to" + + " host " + mConnInfo.getHost() + + " port " + mConnInfo.getPort() + ", " + msg); + } + + protected void finalize() + throws Exception { + reset(); + } + + /** + * used for disconnecting all connections and reset everything to 0 + * as if connections were never made. used just before a subsystem + * shutdown or process exit. + * useful only if no connections are outstanding. + */ + public synchronized void reset() + throws ELdapException { + if (mNumConns == mTotal) { + for (int i = 0; i < mNumConns; i++) { + try { + mConns[i].disconnect(); + } catch (LDAPException e) { + } + mConns[i] = null; + } + if (mMasterConn != null) { + try { + log(ILogger.LL_INFO, "disconnecting masterConn"); + mMasterConn.disconnect(); + } catch (LDAPException e) { + log(ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_LDAPCONN_CANNOT_RESET", + e.toString())); + } + } + mMasterConn = null; + mTotal = 0; + mNumConns = 0; + } else { + CMS.debug( + "Cannot reset factory: connections not all returned"); + throw new ELdapException(CMS.getUserMessage("CMS_LDAP_CANNOT_RESET_CONNFAC")); + } + + if (mAuthInfo != null) { + mAuthInfo.reset(); + } + } + + /** + * return ldap connection info + */ + public LdapConnInfo getConnInfo() { + return mConnInfo; + } + + /** + * return ldap authentication info + */ + public LdapAuthInfo getAuthInfo() { + return mAuthInfo; + } + + /** + * used to keep track of connections from this factory. + */ + public class BoundConnection extends LdapBoundConnection { + /** + * + */ + private static final long serialVersionUID = 1353616391879078337L; + + public BoundConnection(LdapConnInfo connInfo, LdapAuthInfo authInfo) + throws LDAPException { + super(connInfo, authInfo); + } + + public BoundConnection(String host, int port, int version, + LDAPSocketFactory fac, + String bindDN, String bindPW) + throws LDAPException { + super(host, port, version, fac, bindDN, bindPW); + } + + /** + * used only to identify the factory from which this came. + */ + public BoundConnection[] getFacId() { + return mConns; + } + } +} diff --git a/base/common/src/com/netscape/cmscore/ldapconn/LdapBoundConnection.java b/base/common/src/com/netscape/cmscore/ldapconn/LdapBoundConnection.java new file mode 100644 index 000000000..fc97ab48c --- /dev/null +++ b/base/common/src/com/netscape/cmscore/ldapconn/LdapBoundConnection.java @@ -0,0 +1,220 @@ +// --- BEGIN COPYRIGHT BLOCK --- +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; version 2 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// (C) 2007 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- +package com.netscape.cmscore.ldapconn; + +import java.util.Properties; + +import netscape.ldap.LDAPConnection; +import netscape.ldap.LDAPException; +import netscape.ldap.LDAPRebind; +import netscape.ldap.LDAPRebindAuth; +import netscape.ldap.LDAPSocketFactory; +import netscape.ldap.LDAPv2; + +import com.netscape.certsrv.apps.CMS; + +/** + * A LDAP connection that is bound to a server host, port, secure type. + * and authentication. + * Makes a LDAP connection and authentication when instantiated. + * Cannot establish another LDAP connection or authentication after + * construction. LDAPConnection connect and authentication methods are + * overridden to prevent this. + */ +public class LdapBoundConnection extends LDAPConnection { + /** + * + */ + private static final long serialVersionUID = -2242077674357271559L; + // LDAPConnection calls authenticate so must set this for first + // authenticate call. + private boolean mAuthenticated = false; + + /** + * Instantiates a connection to a ldap server, secure or non-secure + * connection with Ldap basic bind dn & pw authentication. + */ + public LdapBoundConnection( + LdapConnInfo connInfo, LdapAuthInfo authInfo) + throws LDAPException { + // this LONG line to satisfy super being the first call. (yuk) + super( + authInfo.getAuthType() == LdapAuthInfo.LDAP_AUTHTYPE_SSLCLIENTAUTH ? + new LdapJssSSLSocketFactory(authInfo.getParms()[0]) : + (connInfo.getSecure() ? new LdapJssSSLSocketFactory() : null)); + + // Set option to automatically follow referrals. + // Use the same credentials to follow referrals; this is the easiest + // thing to do without any complicated configuration using + // different hosts. + // If client auth is used don't have dn and pw to follow referrals. + + boolean followReferrals = connInfo.getFollowReferrals(); + + setOption(LDAPv2.REFERRALS, new Boolean(followReferrals)); + if (followReferrals && + authInfo.getAuthType() != LdapAuthInfo.LDAP_AUTHTYPE_SSLCLIENTAUTH) { + LDAPRebind rebindInfo = + new ARebindInfo(authInfo.getParms()[0], + authInfo.getParms()[1]); + + setOption(LDAPv2.REFERRALS_REBIND_PROC, rebindInfo); + } + + if (authInfo.getAuthType() == LdapAuthInfo.LDAP_AUTHTYPE_SSLCLIENTAUTH) { + // will be bound to client auth cert mapped entry. + super.connect(connInfo.getHost(), connInfo.getPort()); + CMS.debug( + "Established LDAP connection with SSL client auth to " + + connInfo.getHost() + ":" + connInfo.getPort()); + } else { // basic auth + String binddn = authInfo.getParms()[0]; + String bindpw = authInfo.getParms()[1]; + + super.connect(connInfo.getVersion(), + connInfo.getHost(), connInfo.getPort(), binddn, bindpw); + CMS.debug( + "Established LDAP connection using basic authentication to" + + " host " + connInfo.getHost() + + " port " + connInfo.getPort() + + " as " + binddn); + } + } + + /** + * Instantiates a connection to a ldap server, secure or non-secure + * connection with Ldap basic bind dn & pw authentication. + */ + public LdapBoundConnection(String host, int port, int version, + LDAPSocketFactory fac, + String bindDN, String bindPW) + throws LDAPException { + super(fac); + if (bindDN != null) { + super.connect(version, host, port, bindDN, bindPW); + CMS.debug( + "Established LDAP connection using basic authentication " + + " as " + bindDN + " to " + host + ":" + port); + } else { + if (fac == null && bindDN == null) { + throw new IllegalArgumentException( + "Ldap bound connection must have authentication info."); + } + // automatically authenticated if it's ssl client auth. + super.connect(version, host, port, null, null); + CMS.debug( + "Established LDAP connection using SSL client authentication " + + "to " + host + ":" + port); + } + } + + /** + * Overrides same method in LDAPConnection to do prevent re-authentication. + */ + public void authenticate(int version, String dn, String pw) + throws LDAPException { + + /** + * if (mAuthenticated) { + * throw new RuntimeException( + * "this LdapBoundConnection already authenticated: auth(v,dn,pw)"); + * } + **/ + super.authenticate(version, dn, pw); + mAuthenticated = true; + } + + /** + * Overrides same method in LDAPConnection to do prevent re-authentication. + */ + public void authenticate(String dn, String pw) + throws LDAPException { + + /** + * if (mAuthenticated) { + * throw new RuntimeException( + * "this LdapBoundConnection already authenticated: auth(dn,pw)"); + * } + **/ + super.authenticate(3, dn, pw); + mAuthenticated = true; + } + + /** + * Overrides same method in LDAPConnection to do prevent re-authentication. + */ + public void authenticate(String dn, String mech, String packageName, + Properties props, Object getter) + throws LDAPException { + + /** + * if (mAuthenticated) { + * throw new RuntimeException( + * "this LdapBoundConnection already authenticated: auth(mech)"); + * } + **/ + super.authenticate(dn, mech, packageName, props, getter); + mAuthenticated = true; + } + + /** + * Overrides same method in LDAPConnection to do prevent re-authentication. + */ + public void authenticate(String dn, String mechs[], String packageName, + Properties props, Object getter) + throws LDAPException { + + /** + * if (mAuthenticated) { + * throw new RuntimeException( + * "this LdapBoundConnection is already authenticated: auth(mechs)"); + * } + **/ + super.authenticate(dn, mechs, packageName, props, getter); + mAuthenticated = true; + } + + /** + * overrides parent's connect to prevent re-connect. + */ + public void connect(String host, int port) throws LDAPException { + throw new RuntimeException( + "this LdapBoundConnection is already connected: conn(host,port)"); + } + + /** + * overrides parent's connect to prevent re-connect. + */ + public void connect(int version, String host, int port, + String dn, String pw) throws LDAPException { + throw new RuntimeException( + "this LdapBoundConnection is already connected: conn(version,h,p)"); + } +} + +class ARebindInfo implements LDAPRebind { + private LDAPRebindAuth mRebindAuthInfo = null; + + public ARebindInfo(String binddn, String pw) { + mRebindAuthInfo = new LDAPRebindAuth(binddn, pw); + } + + public LDAPRebindAuth getRebindAuthentication(String host, int port) { + return mRebindAuthInfo; + } +} diff --git a/base/common/src/com/netscape/cmscore/ldapconn/LdapConnInfo.java b/base/common/src/com/netscape/cmscore/ldapconn/LdapConnInfo.java new file mode 100644 index 000000000..4ef7d804c --- /dev/null +++ b/base/common/src/com/netscape/cmscore/ldapconn/LdapConnInfo.java @@ -0,0 +1,119 @@ +// --- BEGIN COPYRIGHT BLOCK --- +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; version 2 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// (C) 2007 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- +package com.netscape.cmscore.ldapconn; + +import netscape.ldap.LDAPv2; + +import com.netscape.certsrv.apps.CMS; +import com.netscape.certsrv.base.EBaseException; +import com.netscape.certsrv.base.EPropertyNotFound; +import com.netscape.certsrv.base.IConfigStore; +import com.netscape.certsrv.ldap.ELdapException; +import com.netscape.certsrv.ldap.ILdapConnInfo; + +/** + * class for reading ldap connection from the config store. + * ldap connection info: host, port, secure connection + */ +public class LdapConnInfo implements ILdapConnInfo { + + private String mHost = null; + private int mPort = -1; + private boolean mSecure = false; + private int mVersion = LDAPv2.PROTOCOL_VERSION; + private boolean mFollowReferrals = true; + + /** + * default constructor. must be followed by init(IConfigStore) + */ + public LdapConnInfo(IConfigStore config) throws EBaseException, ELdapException { + init(config); + } + + /** + * initializes an instance from a config store. + * required parms: host, port + * optional parms: secure connection, authentication method & info. + */ + public void init(IConfigStore config) throws EBaseException, ELdapException { + mHost = config.getString(PROP_HOST); + mPort = config.getInteger(PROP_PORT); + String version = (String) config.get(PROP_PROTOCOL); + + if (version != null && version.equals("")) { + // provide a default when this field is blank from the + // configuration. + mVersion = LDAP_VERSION_3; + } else { + mVersion = config.getInteger(PROP_PROTOCOL, LDAP_VERSION_3); + if (mVersion != LDAP_VERSION_2 && mVersion != LDAP_VERSION_3) { + throw new EBaseException( + CMS.getUserMessage("CMS_BASE_INVALID_PROPERTY", PROP_PROTOCOL)); + } + } + if (mHost == null || (mHost.length() == 0) || (mHost.trim().equals(""))) { + throw new EPropertyNotFound(CMS.getUserMessage("CMS_BASE_GET_PROPERTY_FAILED", PROP_HOST)); + } + if (mPort <= 0) { + throw new EBaseException( + CMS.getUserMessage("CMS_BASE_INVALID_PROPERTY", PROP_PORT)); + } + mSecure = config.getBoolean(PROP_SECURE, false); + mFollowReferrals = config.getBoolean(PROP_FOLLOW_REFERRALS, true); + } + + public LdapConnInfo(String host, int port, boolean secure) { + mHost = host; + mPort = port; + mSecure = secure; + if (mHost == null || mPort <= 0) { + // XXX log something here + throw new IllegalArgumentException("LDAP host or port is null"); + } + } + + public LdapConnInfo(String host, int port) { + mHost = host; + mPort = port; + if (mHost == null || mPort <= 0) { + // XXX log something here + throw new IllegalArgumentException("LDAP host or port is null"); + } + } + + public String getHost() { + return mHost; + } + + public int getPort() { + return mPort; + } + + public int getVersion() { + return mVersion; + } + + public boolean getSecure() { + return mSecure; + } + + public boolean getFollowReferrals() { + return mFollowReferrals; + } + +} diff --git a/base/common/src/com/netscape/cmscore/ldapconn/LdapJssSSLSocketFactory.java b/base/common/src/com/netscape/cmscore/ldapconn/LdapJssSSLSocketFactory.java new file mode 100644 index 000000000..4df2fe357 --- /dev/null +++ b/base/common/src/com/netscape/cmscore/ldapconn/LdapJssSSLSocketFactory.java @@ -0,0 +1,109 @@ +// --- BEGIN COPYRIGHT BLOCK --- +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; version 2 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// (C) 2007 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- +package com.netscape.cmscore.ldapconn; + +import java.io.IOException; +import java.net.Socket; +import java.net.UnknownHostException; + +import netscape.ldap.LDAPException; +import netscape.ldap.LDAPSSLSocketFactoryExt; + +import org.mozilla.jss.ssl.SSLHandshakeCompletedEvent; +import org.mozilla.jss.ssl.SSLHandshakeCompletedListener; +import org.mozilla.jss.ssl.SSLSocket; + +import com.netscape.certsrv.apps.CMS; +import com.netscape.certsrv.logging.ILogger; + +/** + * Uses HCL ssl socket. + * + * @author Lily Hsiao lhsiao@netscape.com + */ +public class LdapJssSSLSocketFactory implements LDAPSSLSocketFactoryExt { + private String mClientAuthCertNickname = null; + private boolean mClientAuth = false; + + public LdapJssSSLSocketFactory() { + } + + public LdapJssSSLSocketFactory(String certNickname) { + mClientAuthCertNickname = certNickname; + } + + public Socket makeSocket(String host, int port) throws LDAPException { + SSLSocket s = null; + + try { + SSLSocket.enableSSL2Default(false); + s = new SSLSocket(host, port); + s.setUseClientMode(true); + s.enableSSL2(false); + //TODO Do we really want to set the default each time? + SSLSocket.enableSSL2Default(false); + s.enableV2CompatibleHello(false); + + SSLHandshakeCompletedListener listener = null; + + listener = new ClientHandshakeCB(this); + s.addHandshakeCompletedListener(listener); + + if (mClientAuthCertNickname != null) { + mClientAuth = true; + CMS.debug( + "LdapJssSSLSocket set client auth cert nickname" + + mClientAuthCertNickname); + s.setClientCertNickname(mClientAuthCertNickname); + } + s.forceHandshake(); + } catch (UnknownHostException e) { + log(ILogger.LL_FAILURE, + CMS.getLogMessage("CMSCORE_LDAPCONN_UNKNOWN_HOST")); + throw new LDAPException( + "Cannot Create JSS SSL Socket - Unknown host"); + } catch (IOException e) { + log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_LDAPCONN_IO_ERROR", e.toString())); + throw new LDAPException("IO Error creating JSS SSL Socket"); + } + return s; + } + + public boolean isClientAuth() { + return mClientAuth; + } + + public Object getCipherSuites() { + return null; + } + + public void log(int level, String msg) { + } + + class ClientHandshakeCB implements SSLHandshakeCompletedListener { + Object sc; + + public ClientHandshakeCB(Object sc) { + this.sc = sc; + } + + public void handshakeCompleted(SSLHandshakeCompletedEvent event) { + CMS.debug("SSL handshake happened"); + } + } +} -- cgit