// --- 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 statement // /////////////////////// package com.netscape.cms.publish.mappers; /////////////////////// // import statements // /////////////////////// /* cert server imports */ import java.io.IOException; import java.security.cert.X509Certificate; import java.util.Locale; import java.util.Vector; import netscape.ldap.LDAPAttribute; import netscape.ldap.LDAPAttributeSet; import netscape.ldap.LDAPConnection; import netscape.ldap.LDAPEntry; import netscape.ldap.LDAPException; import netscape.ldap.LDAPSearchResults; import netscape.ldap.LDAPv2; import netscape.ldap.LDAPv3; import netscape.ldap.util.DN; import netscape.security.x509.CertificateExtensions; import netscape.security.x509.X500Name; import netscape.security.x509.X509CRLImpl; import netscape.security.x509.X509CertImpl; import netscape.security.x509.X509CertInfo; import com.netscape.certsrv.apps.CMS; import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.IConfigStore; import com.netscape.certsrv.base.IExtendedPluginInfo; import com.netscape.certsrv.ldap.ELdapException; import com.netscape.certsrv.ldap.ELdapServerDownException; import com.netscape.certsrv.logging.ILogger; import com.netscape.certsrv.publish.ILdapMapper; import com.netscape.certsrv.request.IRequest; ////////////////////// // class definition // ////////////////////// /** * Maps a request to an entry in the LDAP server. * Takes a dnPattern to form the baseDN from the * request attributes and certificate subject name. * Does a base search for the entry in the directory * to publish the cert or crl. The restriction of * this mapper is that the ldap dn components must * be part of certificate subject name or request * attributes or constant. The difference of this * mapper and LdapSimpleMap is that if the ldap * entry is not found, it has the option to create * the ldap entry given the dn and attributes * formulated. * * @version $Revision$, $Date$ */ public class LdapEnhancedMap implements ILdapMapper, IExtendedPluginInfo { //////////////////////// // default parameters // //////////////////////// ////////////////////////////////////// // local LdapEnhancedMap parameters // ////////////////////////////////////// private boolean mInited = false; // the subject DN pattern protected MapDNPattern mPattern = null; // the list of request attriubutes to retrieve protected String[] mReqAttrs = null; // the list of cert attributes to retrieve protected String[] mCertAttrs = null; protected String[] mLdapValues = null; //////////////////////////// // ILdapMapper parameters // //////////////////////////// /* mapper plug-in fields */ protected static final String PROP_DNPATTERN = "dnPattern"; protected static final String PROP_CREATE = "createEntry"; // the object class of the entry to be created. xxxx not done yet protected static final String PROP_OBJCLASS = "objectClass"; // req/cert/ext attribute --> directory attribute table protected static final String PROP_ATTRNUM = "attrNum"; protected static final String PROP_ATTR_NAME = "attrName"; protected static final String PROP_ATTR_PATTERN = "attrPattern"; /* mapper plug-in fields initialization values */ private static final int DEFAULT_NUM_ATTRS = 1; /* Holds mapper plug-in fields accepted by this implementation. * This list is passed to the configuration console so configuration * for instances of this implementation can be configured through the * console. */ private static Vector defaultParams = new Vector(); static { defaultParams.addElement(PROP_DNPATTERN + "="); defaultParams.addElement(PROP_CREATE + "=true"); defaultParams.addElement(PROP_ATTRNUM + "=" + DEFAULT_NUM_ATTRS); for (int i = 0; i < DEFAULT_NUM_ATTRS; i++) { defaultParams.addElement(PROP_ATTR_NAME + i + "="); defaultParams.addElement(PROP_ATTR_PATTERN + i + "="); } } /* mapper plug-in values */ protected String mDnPattern = null; protected boolean mCreateEntry = true; private int mNumAttrs = DEFAULT_NUM_ATTRS; protected String[] mLdapNames = null; protected String[] mLdapPatterns = null; /* miscellaneous constants local to this mapper plug-in */ // default dn pattern if left blank or not set in the config public static final String DEFAULT_DNPATTERN = "UID=$req.HTTP_PARAMS.UID, " + "OU=people, O=$subj.o, C=$subj.c"; private static final int MAX_ATTRS = 10; protected static final int DEFAULT_ATTRNUM = 1; /* miscellaneous variables local to this mapper plug-in */ protected IConfigStore mConfig = null; protected AVAPattern[] mPatterns = null; //////////////////////////////////// // IExtendedPluginInfo parameters // //////////////////////////////////// /////////////////////// // Logger parameters // /////////////////////// private ILogger mLogger = CMS.getLogger(); ///////////////////// // default methods // ///////////////////// /** * Default constructor, initialization must follow. */ public LdapEnhancedMap() { } /////////////////////////////////// // local LdapEnhancedMap methods // /////////////////////////////////// /** * common initialization routine. */ protected void init(String dnPattern) throws EBaseException { if (mInited) { return; } mDnPattern = dnPattern; if (mDnPattern == null || mDnPattern.length() == 0) { mDnPattern = DEFAULT_DNPATTERN; } try { mPattern = new MapDNPattern(mDnPattern); } catch (ELdapException e) { log(ILogger.LL_FAILURE, CMS.getLogMessage("PUBLISH_DN_PATTERN_INIT", dnPattern, e.toString())); throw new EBaseException( "falied to init with pattern " + dnPattern + " " + e); } mInited = true; } /** * form a dn from component in the request and cert subject name * * @param req The request * @param obj The certificate or crl */ private String formDN(IRequest req, Object obj) throws EBaseException { CertificateExtensions certExt = null; X500Name subjectDN = null; try { X509Certificate cert = (X509Certificate) obj; subjectDN = (X500Name) ((X509Certificate) cert).getSubjectDN(); CMS.debug( "LdapEnhancedMap: cert subject dn:" + subjectDN.toString()); //certExt = (CertificateExtensions) // ((X509CertImpl)cert).get( // X509CertInfo.EXTENSIONS); X509CertInfo info = (X509CertInfo) ((X509CertImpl) cert).get( X509CertImpl.NAME + "." + X509CertImpl.INFO); certExt = (CertificateExtensions) info.get(CertificateExtensions.NAME); } catch (java.security.cert.CertificateParsingException e) { log(ILogger.LL_FAILURE, CMS.getLogMessage("PUBLISH_CANT_GET_EXT", e.toString())); } catch (IOException e) { log(ILogger.LL_FAILURE, CMS.getLogMessage("PUBLISH_CANT_GET_EXT", e.toString())); } catch (java.security.cert.CertificateException e) { log(ILogger.LL_FAILURE, CMS.getLogMessage("PUBLISH_CANT_GET_EXT", e.toString())); } catch (ClassCastException e) { try { X509CRLImpl crl = (X509CRLImpl) obj; subjectDN = (X500Name) ((X509CRLImpl) crl).getIssuerDN(); CMS.debug( "LdapEnhancedMap: crl issuer dn: " + subjectDN.toString()); } catch (ClassCastException ex) { log(ILogger.LL_FAILURE, CMS.getLogMessage("PUBLISH_PUBLISH_OBJ_NOT_SUPPORTED", ((req == null) ? "" : req.getRequestId().toString()))); return null; } } try { mLdapValues = new String[mNumAttrs]; for (int i = 0; i < mNumAttrs; i++) { if (mPatterns[i] != null) { mLdapValues[i] = mPatterns[i].formAVA( req, subjectDN, certExt); } } String dn = mPattern.formDN(req, subjectDN, certExt); return dn; } catch (ELdapException e) { log(ILogger.LL_FAILURE, CMS.getLogMessage("PUBLISH_CANT_FORM_DN", ((req == null) ? "" : req.getRequestId().toString()), e.toString())); throw new EBaseException( "failed to form dn for request: " + ((req == null) ? "" : req.getRequestId().toString()) + " " + e); } } private void createEntry(LDAPConnection conn, String dn) throws LDAPException { LDAPAttributeSet attrs = new LDAPAttributeSet(); // OID 2.5.6.16 String caOc[] = { "top", "person", "organizationalPerson", "inetOrgPerson" }; DN dnobj = new DN(dn); String attrval[] = dnobj.explodeDN(true); attrs.add(new LDAPAttribute("cn", attrval[0])); attrs.add(new LDAPAttribute("sn", attrval[0])); attrs.add(new LDAPAttribute("objectclass", caOc)); for (int i = 0; i < mNumAttrs; i++) { if (mLdapNames[i] != null && !mLdapNames[i].trim().equals("") && mLdapValues[i] != null && !mLdapValues[i].trim().equals("")) { attrs.add(new LDAPAttribute(mLdapNames[i], mLdapValues[i])); } } LDAPEntry entry = new LDAPEntry(dn, attrs); conn.add(entry); } ///////////////////////// // ILdapMapper methods // ///////////////////////// /** * for initializing from config store. * * implementation for extended * ILdapPlugin interface method */ public void init(IConfigStore config) throws EBaseException { mConfig = config; mDnPattern = mConfig.getString(PROP_DNPATTERN, DEFAULT_DNPATTERN); mCreateEntry = mConfig.getBoolean(PROP_CREATE, true); mNumAttrs = mConfig.getInteger(PROP_ATTRNUM, 0); mLdapNames = new String[mNumAttrs]; mLdapPatterns = new String[mNumAttrs]; mPatterns = new AVAPattern[mNumAttrs]; for (int i = 0; i < mNumAttrs; i++) { mLdapNames[i] = mConfig.getString(PROP_ATTR_NAME + Integer.toString(i), ""); mLdapPatterns[i] = mConfig.getString(PROP_ATTR_PATTERN + Integer.toString(i), ""); if (mLdapPatterns[i] != null && !mLdapPatterns[i].trim().equals("")) { mPatterns[i] = new AVAPattern(mLdapPatterns[i]); } } init(mDnPattern); } /** * implementation for extended * ILdapPlugin interface method */ public IConfigStore getConfigStore() { return mConfig; } public String getImplName() { return "LdapEnhancedMap"; } public String getDescription() { return "LdapEnhancedMap"; } public Vector getDefaultParams() { return defaultParams; } public Vector getInstanceParams() { Vector v = new Vector(); try { if (mDnPattern == null) { v.addElement(PROP_DNPATTERN + "="); } else { v.addElement(PROP_DNPATTERN + "=" + mConfig.getString(PROP_DNPATTERN)); } v.addElement(PROP_CREATE + "=" + mConfig.getBoolean(PROP_CREATE, true)); v.addElement(PROP_ATTRNUM + "=" + mConfig.getInteger(PROP_ATTRNUM, DEFAULT_NUM_ATTRS)); for (int i = 0; i < mNumAttrs; i++) { if (mLdapNames[i] != null) { v.addElement(PROP_ATTR_NAME + i + "=" + mLdapNames[i]); } else { v.addElement(PROP_ATTR_NAME + i + "="); } if (mLdapPatterns[i] != null) { v.addElement(PROP_ATTR_PATTERN + i + "=" + mLdapPatterns[i]); } else { v.addElement(PROP_ATTR_PATTERN + i + "="); } } } catch (Exception e) { } return v; } /** * Maps an X500 subject name to an LDAP entry. * Uses DN pattern to form a DN for an LDAP base search. * * @param conn the LDAP connection. * @param obj the object to map. * @exception ELdapException if any LDAP exceptions occurred. */ public String map(LDAPConnection conn, Object obj) throws ELdapException { return map(conn, null, obj); } /** * Maps an X500 subject name to an LDAP entry. * Uses DN pattern to form a DN for an LDAP base search. * * @param conn the LDAP connection. * @param req the request to map. * @param obj the object to map. * @exception ELdapException if any LDAP exceptions occurred. */ public String map(LDAPConnection conn, IRequest req, Object obj) throws ELdapException { if (conn == null) { return null; } String dn = null; try { dn = formDN(req, obj); if (dn == null) { log(ILogger.LL_FAILURE, CMS.getLogMessage("PUBLISH_DN_NOT_FORMED")); String s1 = ""; if (req != null) s1 = req.getRequestId().toString(); throw new ELdapException( CMS.getUserMessage("CMS_LDAP_NO_DN_MATCH", s1)); } int scope = LDAPv2.SCOPE_BASE; String filter = "(objectclass=*)"; // search for entry String[] attrs = new String[] { LDAPv3.NO_ATTRS }; log(ILogger.LL_INFO, "searching for dn: " + dn + " filter:" + filter + " scope: base"); LDAPSearchResults results = conn.search(dn, scope, filter, attrs, false); LDAPEntry entry = results.next(); if (results.hasMoreElements()) { log(ILogger.LL_FAILURE, CMS.getLogMessage("PUBLISH_MORE_THAN_ONE_ENTRY", dn + ((req == null) ? "" : req.getRequestId().toString()))); throw new ELdapException( CMS.getUserMessage("CMS_LDAP_MORE_THAN_ONE_ENTRY", ((req == null) ? "" : req.getRequestId().toString()))); } if (entry != null) { return entry.getDN(); } else { log(ILogger.LL_FAILURE, CMS.getLogMessage("PUBLISH_ENTRY_NOT_FOUND", dn + ((req == null) ? "" : req.getRequestId().toString()))); throw new ELdapException(CMS.getUserMessage("CMS_LDAP_NO_MATCH_FOUND", "null entry")); } } 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("PUBLISH_NO_LDAP_SERVER")); throw new ELdapServerDownException(CMS.getUserMessage("CMS_LDAP_SERVER_UNAVAILABLE", conn.getHost(), "" + conn.getPort())); } else if (e.getLDAPResultCode() == LDAPException.NO_SUCH_OBJECT && mCreateEntry) { try { createEntry(conn, dn); log(ILogger.LL_INFO, "Entry " + dn + " Created"); return dn; } catch (LDAPException e1) { log(ILogger.LL_FAILURE, CMS.getLogMessage("PUBLISH_DN_MAP_EXCEPTION", dn, e.toString())); log(ILogger.LL_FAILURE, "Entry is not created. " + "This may because there are " + "entries in the directory " + "hierachy not exit."); throw new ELdapException( CMS.getUserMessage("CMS_LDAP_CREATE_ENTRY", dn)); } } else { log(ILogger.LL_FAILURE, CMS.getLogMessage("PUBLISH_DN_MAP_EXCEPTION", dn, e.toString())); throw new ELdapException(CMS.getUserMessage("CMS_LDAP_NO_MATCH_FOUND", e.toString())); } } catch (EBaseException e) { log(ILogger.LL_FAILURE, CMS.getLogMessage("PUBLISH_EXCEPTION_CAUGHT", e.toString())); throw new ELdapException(CMS.getUserMessage("CMS_LDAP_NO_MATCH_FOUND", e.toString())); } } ///////////////////////////////// // IExtendedPluginInfo methods // ///////////////////////////////// public String[] getExtendedPluginInfo(Locale locale) { Vector v = new Vector(); v.addElement(PROP_DNPATTERN + ";string;Describes how to form the Ldap " + "Subject name in the directory. " + "Example 1: 'uid=CertMgr, o=Fedora'. " + "Example 2: 'uid=$req.HTTP_PARAMS.uid, " + "E=$ext.SubjectAlternativeName.RFC822Name, " + "ou=$subj.ou'. " + "$req means: take the attribute from the " + "request. " + "$subj means: take the attribute from the " + "certificate subject name. " + "$ext means: take the attribute from the " + "certificate extension"); v.addElement(PROP_CREATE + ";boolean;If checked, An entry will be " + "created automatically"); v.addElement(PROP_ATTRNUM + ";string;How many attributes to add."); v.addElement(IExtendedPluginInfo.HELP_TOKEN + ";configuration-ldappublish-mapper-enhancedmapper"); v.addElement(IExtendedPluginInfo.HELP_TEXT + ";Describes how to form the LDAP DN of the " + "entry to publish to"); for (int i = 0; i < MAX_ATTRS; i++) { v.addElement(PROP_ATTR_NAME + Integer.toString(i) + ";string;" + "The name of LDAP attribute " + "to be added. e.g. mail"); v.addElement(PROP_ATTR_PATTERN + Integer.toString(i) + ";string;" + "How to create the LDAP attribute value. " + "e.g. $req.HTTP_PARAMS.csrRequestorEmail, " + "$subj.E or " + "$ext.SubjectAlternativeName.RFC822Name"); } String params[] = com.netscape.cmsutil.util.Utils.getStringArrayFromVector(v); return params; } //////////////////// // Logger methods // //////////////////// private void log(int level, String msg) { mLogger.log(ILogger.EV_SYSTEM, ILogger.S_LDAP, level, "LdapEnhancedMapper: " + msg); } }