From 293c73617e69a192b23fea44d0f2dee43d9eee6b Mon Sep 17 00:00:00 2001 From: William Brown Date: Tue, 14 Nov 2017 16:49:38 +1000 Subject: [PATCH 4/9] Ticket 49218 - Certmap - plugin v4 dylib handlers and routines Bug Description: This adds support for pluggable certificate mapping libraries. To achieve this, this replaces the existing baked in certificate mapping code. Fix Description: Add support for opening and managing dylibs transactionally and safely with cow structures. ADditionally this provides the support for txns in operations related to plugin types that require it. https://pagure.io/389-ds-base/issue/49218 https://pagure.io/lib389/issue/95 https://pagure.io/lib389/issue/84 Author: wibrown Review by: ??? --- ldap/servers/slapd/auth.c | 466 ++-------------- ldap/servers/slapd/config.c | 27 +- ldap/servers/slapd/configdse.c | 21 + ldap/servers/slapd/connection.c | 18 + ldap/servers/slapd/daemon.c | 10 + ldap/servers/slapd/dse.c | 37 +- ldap/servers/slapd/fedse.c | 36 ++ ldap/servers/slapd/libglobs.c | 6 +- ldap/servers/slapd/main.c | 33 +- ldap/servers/slapd/operation.c | 1 + ldap/servers/slapd/pblock.c | 4 + ldap/servers/slapd/plugin_v4.c | 1171 +++++++++++++++++++++++++++++++++++++++ 12 files changed, 1392 insertions(+), 438 deletions(-) create mode 100644 ldap/servers/slapd/plugin_v4.c diff --git a/ldap/servers/slapd/auth.c b/ldap/servers/slapd/auth.c index b8e171b..12e3b7c 100644 --- a/ldap/servers/slapd/auth.c +++ b/ldap/servers/slapd/auth.c @@ -20,334 +20,7 @@ #include /* MAXPATHLEN */ #include "slap.h" /* slapi_ch_malloc */ #include "fe.h" - -char *client_auth_config_file = NULL; - -/* forward declarations */ - -static void generate_id(void); -static Slapi_ComponentId *auth_get_component_id(void); - -#define internal_ld NULL - -static int LDAP_CALL LDAP_CALLBACK -slapu_msgfree(LDAP *ld, LDAPMessage *msg) -{ - Slapi_PBlock *pb = (Slapi_PBlock *)msg; - if (ld != internal_ld) { - return ldap_msgfree(msg); - } - if (pb) { - slapi_free_search_results_internal(pb); - slapi_pblock_destroy(pb); - } - return LDAP_SUCCESS; -} - -static int LDAP_CALL LDAP_CALLBACK -slapu_search_s(LDAP *ld, const char *rawbaseDN, int scope, const char *filter, char **attrs, int attrsonly, LDAPMessage **result) -{ - int err = LDAP_NO_SUCH_OBJECT; - Slapi_PBlock *pb = NULL; - LDAPControl **ctrls; - Slapi_DN *sdn = slapi_sdn_new_dn_byval(rawbaseDN); - const char *baseDN = slapi_sdn_get_dn(sdn); - - if (ld != internal_ld) { - err = ldap_search_ext_s(ld, baseDN, scope, filter, attrs, attrsonly, - NULL, NULL, NULL, -1, result); - slapi_sdn_free(&sdn); - return err; - } - slapi_log_err(SLAPI_LOG_TRACE, "slapu_search_s", "=> (\"%s\", %i, %s)\n", - baseDN, scope, filter); - if (filter == NULL) - filter = "objectclass=*"; - - /* use new internal search API */ - pb = slapi_pblock_new(); - /* we need to provide managedsait control to avoid returning continuation references */ - ctrls = (LDAPControl **)slapi_ch_calloc(2, sizeof(LDAPControl *)); - ctrls[0] = (LDAPControl *)slapi_ch_malloc(sizeof(LDAPControl)); - ctrls[0]->ldctl_oid = slapi_ch_strdup(LDAP_CONTROL_MANAGEDSAIT); - ctrls[0]->ldctl_value.bv_val = NULL; - ctrls[0]->ldctl_value.bv_len = 0; - ctrls[0]->ldctl_iscritical = '\0'; - slapi_search_internal_set_pb(pb, baseDN, scope, (char *)filter, attrs, - attrsonly, ctrls, NULL, - auth_get_component_id(), 0 /* actions */); - slapi_search_internal_pb(pb); - - if (pb != NULL) { - if (slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &err)) { - err = LDAP_LOCAL_ERROR; - } - if (err != LDAP_SUCCESS) { - slapu_msgfree(ld, (LDAPMessage *)pb); - pb = NULL; - if (scope == LDAP_SCOPE_SUBTREE) { - char fbuf[BUFSIZ]; - slapi_log_err(SLAPI_LOG_ERR, "slapu_search_s", "(\"%s\", subtree, %s) err %i\n", - baseDN, escape_string((char *)filter, fbuf), err); - } - } - } else { - char fbuf[BUFSIZ]; - slapi_log_err(SLAPI_LOG_ERR, "slapu_search_s", "(\"%s\", %i, %s) NULL\n", - baseDN, scope, escape_string((char *)filter, fbuf)); - } - slapi_sdn_free(&sdn); - *result = (LDAPMessage *)pb; - slapi_log_err(SLAPI_LOG_TRACE, "<= slapu_search_s", "%i\n", err); - return err; -} - -static int LDAP_CALL LDAP_CALLBACK -slapu_count_entries(LDAP *ld, LDAPMessage *msg) -{ - Slapi_Entry **entry = NULL; - int count = 0; - if (ld != internal_ld) { - return ldap_count_entries(ld, msg); - } - if (!slapi_pblock_get((Slapi_PBlock *)msg, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entry) && entry) { - for (; *entry; ++entry) - ++count; - } - return count; -} - -/* slapu_search_s() returns a Slapi_PBlock*, but slapu_first_entry() and - * slapu_next_entry() return a Slapi_Entry** pointing into the same array - * as the PBlock. If one of the iteration (Slapi_Entry**) pointers was - * passed to slapu_msgfree(), havoc would ensue. ldaputil never does this. - * But ldap_msgfree() would support it (no?); so a plugin function might. - * Yet another way this doesn't support plugin functions. - */ - -static LDAPMessage *LDAP_CALL LDAP_CALLBACK -slapu_first_entry(LDAP *ld, LDAPMessage *msg) -{ - Slapi_Entry **entry = NULL; - if (ld != internal_ld) { - return ldap_first_entry(ld, msg); - } - if (!slapi_pblock_get((Slapi_PBlock *)msg, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entry) && entry && *entry) { - return (LDAPMessage *)entry; - } - return NULL; -} - -static LDAPMessage *LDAP_CALL LDAP_CALLBACK -slapu_next_entry(LDAP *ld, LDAPMessage *msg) -{ - Slapi_Entry **entry = (Slapi_Entry **)msg; - if (ld != internal_ld) { - if (msg) { - return ldap_next_entry(ld, msg); - } else { - return NULL; - } - } - if (entry && *entry && *++entry) { - return (LDAPMessage *)entry; - } - return NULL; -} - -static char *LDAP_CALL LDAP_CALLBACK -slapu_get_dn(LDAP *ld, LDAPMessage *entry) -{ - if (ld != internal_ld) { - return ldap_get_dn(ld, entry); - } - return slapi_ch_strdup(slapi_entry_get_dn(*(Slapi_Entry **)entry)); -} - -static void LDAP_CALL LDAP_CALLBACK -slapu_memfree(LDAP *ld, void *dn) -{ - if (ld != internal_ld) { - ldap_memfree(dn); - } else { - free(dn); - } -} - -static char * -slapu_attr_get_desc(Slapi_Attr *attr) -{ - char *desc = NULL; - if (slapi_attr_get_type(attr, &desc) == LDAP_SUCCESS && desc) { - return slapi_ch_strdup(desc); - } - return NULL; -} - -/* slapu_first_attribute and slapu_next_attribute use a Slapi_Attr* - * as an iterator. It is malloc'd by first() and free'd by ber_free(). - */ - -static char *LDAP_CALL LDAP_CALLBACK -slapu_first_attribute(LDAP *ld, LDAPMessage *entry, BerElement **iter) -{ - if (ld != internal_ld) { - return ldap_first_attribute(ld, entry, iter); - } else { - Slapi_Attr **attr = (Slapi_Attr **)slapi_ch_malloc(sizeof(Slapi_Attr *)); - *iter = (BerElement *)attr; - if (attr && slapi_entry_first_attr(*(Slapi_Entry **)entry, attr) == LDAP_SUCCESS) { - return slapu_attr_get_desc(*attr); - } - } - return NULL; -} - -static char *LDAP_CALL LDAP_CALLBACK -slapu_next_attribute(LDAP *ld, LDAPMessage *entry, BerElement *iter) -{ - Slapi_Attr **attr = (Slapi_Attr **)iter; - if (ld != internal_ld) { - return ldap_next_attribute(ld, entry, iter); - } - if (attr && slapi_entry_next_attr(*(Slapi_Entry **)entry, *attr, attr) == LDAP_SUCCESS) { - return slapu_attr_get_desc(*attr); - } - return NULL; -} - -static void LDAP_CALL LDAP_CALLBACK -slapu_ber_free(LDAP *ld, BerElement *iter, int freebuf) -{ - if (ld != internal_ld) { - ber_free(iter, freebuf); - } else { - free((Slapi_Attr **)iter); - } -} - -static struct berval **LDAP_CALL LDAP_CALLBACK -slapu_get_values_len(LDAP *ld, LDAPMessage *entry, const char *desc) -{ - Slapi_Attr *attr = NULL; - if (ld != internal_ld) { - return ldap_get_values_len(ld, entry, desc); - } - if (slapi_entry_attr_find(*(Slapi_Entry **)entry, desc, &attr) == LDAP_SUCCESS && attr) { - struct berval **values = NULL; - if (slapi_attr_get_bervals_copy(attr, &values) == 0) { - return (values); - } - } - return NULL; -} - -static void LDAP_CALL LDAP_CALLBACK -slapu_value_free_len(LDAP *ld, struct berval **values) -{ - if (ld != internal_ld) { - ldap_value_free_len(values); - } else { - ber_bvecfree(values); - } -} - -void -client_auth_init() -{ - int err; - if (client_auth_config_file == NULL) { - char *confdir = config_get_configdir(); - if (NULL == confdir) { - slapi_log_err(SLAPI_LOG_ERR, "client_auth_init", "Failed to get configdir\n"); - return; - } - client_auth_config_file = PR_smprintf("%s/certmap.conf", confdir); - if (NULL == client_auth_config_file) { - slapi_log_err(SLAPI_LOG_ERR, "client_auth_init", "Failed to duplicate \"%s/certmap\"\n", confdir); - slapi_ch_free_string(&confdir); - return; - } - slapi_ch_free_string(&confdir); - } - err = ldaputil_init(client_auth_config_file, "", NULL, "slapd", NULL); - if (err != LDAPU_SUCCESS) { - slapi_log_err(SLAPI_LOG_TRACE, "client_auth_init", "ldaputil_init(%s,...) %i\n", client_auth_config_file, err); - } else { - LDAPUVTable_t vtable = { - NULL /* ssl_init */, - NULL /* set_option */, - NULL /* simple_bind_s */, - NULL /* unbind */, - slapu_search_s, - slapu_count_entries, - slapu_first_entry, - slapu_next_entry, - slapu_msgfree, - slapu_get_dn, - slapu_memfree, - slapu_first_attribute, - slapu_next_attribute, - slapu_ber_free, - NULL /* get_values */, - NULL /* value_free */, - slapu_get_values_len, - slapu_value_free_len}; - ldapu_VTable_set(&vtable); - } - - /* Generate a component id for cert-based authentication */ - generate_id(); -} - -#include -#include "slapi-plugin.h" /* SLAPI_BERVAL_EQ */ -#include "slapi-private.h" /* COMPONENT_CERT_AUTH */ - -static Slapi_ComponentId *auth_component_id = NULL; - -static void -generate_id(void) -{ - if (auth_component_id == NULL) { - auth_component_id = generate_componentid(NULL /* Not a plugin */, COMPONENT_CERT_AUTH); - } -} - -static Slapi_ComponentId * -auth_get_component_id(void) -{ - return auth_component_id; -} - - -static char * -subject_of(CERTCertificate *cert) -{ - char *dn = NULL; - if (cert != NULL) { - int err = ldapu_get_cert_subject_dn(cert, &dn); - if (err != LDAPU_SUCCESS) { - slapi_log_err(SLAPI_LOG_ERR, "subject_of", "ldapu_get_cert_subject_dn(%p) %i (%s)\n", - (void *)cert, err, ldapu_err2string(err)); - } - } - return dn; -} - -static char * -issuer_of(CERTCertificate *cert) -{ - char *dn = NULL; - if (cert != NULL) { - int err = ldapu_get_cert_issuer_dn(cert, &dn); - if (err != LDAPU_SUCCESS) { - slapi_log_err(SLAPI_LOG_ERR, "issuer_of", "ldapu_get_cert_issuer_dn(%p) %i (%s)\n", - (void *)cert, err, ldapu_err2string(err)); - } - } - return dn; -} +#include /* * Log a certificate that was rejected because the client didn't @@ -365,19 +38,22 @@ handle_bad_certificate(void *clientData, PRFileDesc *prfd) CERTCertificate *clientCert = slapd_ssl_peerCertificate(prfd); PRErrorCode errorCode = PR_GetError(); - char *subject = subject_of(clientCert); - char *issuer = issuer_of(clientCert); + char *subject = slapi_v4_cert_get_subjectdn(clientCert); + char *issuer = slapi_v4_cert_get_issuerdn(clientCert); slapi_log_access(LDAP_DEBUG_STATS, "conn=%" PRIu64 " " SLAPI_COMPONENT_NAME_NSPR " error %i (%s); unauthenticated client %s; issuer %s\n", conn->c_connid, errorCode, slapd_pr_strerror(errorCode), subject ? escape_string(subject, sbuf) : "NULL", issuer ? escape_string(issuer, ibuf) : "NULL"); - if (issuer) + if (issuer) { free(issuer); - if (subject) + } + if (subject) { free(subject); - if (clientCert) + } + if (clientCert) { CERT_DestroyCertificate(clientCert); + } return -1; /* non-zero means reject this certificate */ } @@ -395,7 +71,6 @@ handle_handshake_done(PRFileDesc *prfd, void *clientData) Connection *conn = (Connection *)clientData; CERTCertificate *clientCert = slapd_ssl_peerCertificate(prfd); - char *clientDN = NULL; int keySize = 0; char *cipher = NULL; char *extraErrorMsg = ""; @@ -420,7 +95,7 @@ handle_handshake_done(PRFileDesc *prfd, void *clientData) } keySize = cipherInfo.effectiveKeyBits; - cipher = slapi_ch_strdup(cipherInfo.symCipherName); + cipher = strdup(cipherInfo.symCipherName); /* If inside an Start TLS operation, perform the privacy level discovery * and if the security degree achieved after the handshake is not reckoned @@ -445,85 +120,66 @@ handle_handshake_done(PRFileDesc *prfd, void *clientData) conn->c_connid, sslversion, keySize, cipher ? cipher : "NULL"); } else { - subject = subject_of(clientCert); - if (!subject) { - (void)slapi_getSSLVersion_str(channelInfo.protocolVersion, - sslversion, sizeof(sslversion)); - slapi_log_access(LDAP_DEBUG_STATS, - "conn=%" PRIu64 " %s %i-bit %s; missing subject\n", - conn->c_connid, - sslversion, keySize, cipher ? cipher : "NULL"); + + slapi_v4_certmap_pblock *pbc = slapi_v4_certmap_pblock_init(); + /* + * Populate all the parameters to pbc that are needed. + * + * This is where we actually extract all the certificate details we'll need for + * the certmap bind process to operate. + * + * We need: issuer + * : subject dn + * : raw DER? + * What if we just attach the cert to the pbc, then we can call as needed? + */ + slapi_v4_certmap_pblock_set_clientcert(pbc, clientCert); + + plugin_v4_result bind_certmap_result = plugin_v4_call_bind_certmap(pbc); + if (bind_certmap_result != PLUGIN_V4_SUCCESS) { + slapi_log_err(SLAPI_LOG_ERR, "handle_handshake_done", "Failed to execute bind_certmap plugins\n"); + slapi_v4_certmap_pblock_destroy(pbc); goto done; } - { - char *issuer = issuer_of(clientCert); - char sbuf[BUFSIZ], ibuf[BUFSIZ]; - (void)slapi_getSSLVersion_str(channelInfo.protocolVersion, - sslversion, sizeof(sslversion)); - slapi_log_access(LDAP_DEBUG_STATS, - "conn=%" PRIu64 " %s %i-bit %s; client %s; issuer %s\n", - conn->c_connid, - sslversion, keySize, cipher ? cipher : "NULL", - subject ? escape_string(subject, sbuf) : "NULL", - issuer ? escape_string(issuer, ibuf) : "NULL"); - if (issuer) - free(issuer); + + Slapi_DN *client_dn = slapi_v4_certmap_pblock_get_clientdn(pbc); + + if (client_dn == NULL) { + /* + * Given we got a SUCCESS from the plugin, this shouldn't happen, but + * who knows what kind of junk people write. + */ + slapi_v4_certmap_pblock_destroy(pbc); + slapi_log_err(SLAPI_LOG_ERR, "handle_handshake_done", "bind_certmap plugins return NULL result, this is a mistake!!!!\n"); + goto done; } - slapi_dn_normalize(subject); - { - LDAPMessage *chain = NULL; - char *basedn = config_get_basedn(); - int err; + /* As pointless as this seems, I want to wait to change connection->c_dn to sdn */ + char *binddn = strdup(slapi_sdn_get_dn(client_dn)); - err = ldapu_cert_to_ldap_entry(clientCert, internal_ld, basedn ? basedn : "" /*baseDN*/, &chain); - if (err == LDAPU_SUCCESS && chain) { - LDAPMessage *entry = slapu_first_entry(internal_ld, chain); - if (entry) { - /* clientDN is duplicated in slapu_get_dn */ - clientDN = slapu_get_dn(internal_ld, entry); - } else { + (void)slapi_getSSLVersion_str(channelInfo.protocolVersion, sslversion, sizeof(sslversion)); - extraErrorMsg = "no entry"; - slapi_log_err(SLAPI_LOG_TRACE, "handle_handshake_done", "<= ldapu_cert_to_ldap_entry() %s\n", - extraErrorMsg); - } - } else { - extraErrorMsg = ldapu_err2string(err); - slapi_log_err(SLAPI_LOG_TRACE, "handle_handshake_done", "<= ldapu_cert_to_ldap_entry() %i (%s)%s\n", - err, extraErrorMsg, chain ? "" : " NULL"); - } - slapi_ch_free_string(&basedn); - slapu_msgfree(internal_ld, chain); + if (client_dn != NULL) { + slapi_log_access(LDAP_DEBUG_STATS, + "conn=%" PRIu64 " %s client bound as %s\n", + conn->c_connid, + sslversion, binddn); + } else if (clientCert != NULL) { + slapi_log_access(LDAP_DEBUG_STATS, + "conn=%" PRIu64 " %s failed to map client " + "certificate to LDAP DN (%s)\n", + conn->c_connid, + sslversion, extraErrorMsg); } - } - if (clientDN != NULL) { - Slapi_DN *sdn = NULL; - sdn = slapi_sdn_new_dn_passin(clientDN); - clientDN = slapi_ch_strdup(slapi_sdn_get_dn(sdn)); - slapi_sdn_free(&sdn); - (void)slapi_getSSLVersion_str(channelInfo.protocolVersion, - sslversion, sizeof(sslversion)); - slapi_log_access(LDAP_DEBUG_STATS, - "conn=%" PRIu64 " %s client bound as %s\n", - conn->c_connid, - sslversion, clientDN); - } else if (clientCert != NULL) { - (void)slapi_getSSLVersion_str(channelInfo.protocolVersion, - sslversion, sizeof(sslversion)); - slapi_log_access(LDAP_DEBUG_STATS, - "conn=%" PRIu64 " %s failed to map client " - "certificate to LDAP DN (%s)\n", - conn->c_connid, - sslversion, extraErrorMsg); + /* + * Associate the new credentials with the connection. Note that + * clientDN and clientCert may be NULL. + */ + bind_credentials_set_nolock(conn, SLAPD_AUTH_SSL, binddn, SLAPD_AUTH_SSL, binddn, clientCert, NULL); + + slapi_v4_certmap_pblock_destroy(pbc); } - /* - * Associate the new credentials with the connection. Note that - * clientDN and clientCert may be NULL. - */ - bind_credentials_set_nolock(conn, SLAPD_AUTH_SSL, clientDN, - SLAPD_AUTH_SSL, clientDN, clientCert, NULL); done: slapi_ch_free_string(&subject); slapi_ch_free_string(&cipher); diff --git a/ldap/servers/slapd/config.c b/ldap/servers/slapd/config.c index afe07df..821ca73 100644 --- a/ldap/servers/slapd/config.c +++ b/ldap/servers/slapd/config.c @@ -27,7 +27,7 @@ #include #include -#define MAXARGS 1000 +#include extern int should_detach; extern Slapi_PBlock *repl_pb; @@ -303,14 +303,10 @@ slapd_bootstrap_config(const char *configdir) } #endif /* see if the entry is a child of the plugin base dn */ - if (slapi_sdn_isparent(&plug_dn, - slapi_entry_get_sdn_const(e))) { - if (entry_has_attr_and_value(e, "objectclass", - "nsSlapdPlugin", 0) && - (entry_has_attr_and_value(e, ATTR_PLUGIN_TYPE, - "syntax", 0) || - entry_has_attr_and_value(e, ATTR_PLUGIN_TYPE, - "matchingrule", 0))) { + if (slapi_sdn_isparent(&plug_dn, slapi_entry_get_sdn_const(e))) { + if (entry_has_attr_and_value(e, "objectclass", "nsSlapdPlugin", 0) && + (entry_has_attr_and_value(e, ATTR_PLUGIN_TYPE, "syntax", 0) || + entry_has_attr_and_value(e, ATTR_PLUGIN_TYPE, "matchingrule", 0))) { /* add the syntax/matching scheme rule plugin */ if (plugin_setup(e, 0, 0, 1, returntext)) { slapi_log_err(SLAPI_LOG_ERR, "slapd_bootstrap_config", @@ -320,6 +316,16 @@ slapd_bootstrap_config(const char *configdir) slapi_sdn_done(&plug_dn); goto bail; } + } else if (entry_has_attr_and_value(e, "objectclass", "nsDylibPlugin4", 0)) { + plugin_v4_result p_result = plugin_v4_setup(e); + if (p_result != PLUGIN_V4_SUCCESS) { + slapi_log_err(SLAPI_LOG_ERR, "slapd_bootstrap_config", + "The plugin entry [%s] in the configfile %s was invalid. %d\n", + slapi_entry_get_dn(e), configfile, p_result); + rc = 0; + slapi_sdn_done(&plug_dn); + goto bail; + } } } @@ -495,8 +501,9 @@ slapd_bootstrap_config(const char *configdir) val[0] = 0; } - if (e) + if (e) { slapi_entry_free(e); + } } /* kexcoff: initialize rootpwstoragescheme and pw_storagescheme * if not explicilty set in the config file diff --git a/ldap/servers/slapd/configdse.c b/ldap/servers/slapd/configdse.c index 28160d3..f885545 100644 --- a/ldap/servers/slapd/configdse.c +++ b/ldap/servers/slapd/configdse.c @@ -22,6 +22,7 @@ #include "log.h" #include "slap.h" #include "pw.h" +#include static int check_all_maxdiskspace_and_mlogsize(Slapi_PBlock *pb, LDAPMod **mod, char *returntext); static int is_delete_a_replace(LDAPMod **mods, int mod_count); @@ -369,6 +370,26 @@ load_plugin_entry(Slapi_PBlock *pb __attribute__((unused)), } int +load_plugin_v4_entry(Slapi_PBlock *pb __attribute__((unused)), + Slapi_Entry* e, + Slapi_Entry* ignored __attribute__((unused)), + int *returncode, + char *returntext __attribute__((unused)), + void *arg __attribute__((unused))) +{ + plugin_v4_result p_result = plugin_v4_setup(e); + if (p_result != PLUGIN_V4_SUCCESS) { + slapi_log_err(SLAPI_LOG_ERR, "load_plugin_v4_entry", + "The plugin entry [%s] is invalid. %d\n", + slapi_entry_get_dn(e), p_result); + exit(1); + } + + *returncode = (int)p_result; + return SLAPI_DSE_CALLBACK_OK; +} + +int modify_config_dse(Slapi_PBlock *pb, Slapi_Entry *entryBefore __attribute__((unused)), Slapi_Entry *e, diff --git a/ldap/servers/slapd/connection.c b/ldap/servers/slapd/connection.c index 0128986..73da21d 100644 --- a/ldap/servers/slapd/connection.c +++ b/ldap/servers/slapd/connection.c @@ -27,6 +27,8 @@ #include /* for TCP_CORK */ #endif +#include + typedef Connection work_q_item; static void connection_threadmain(void); static void connection_add_operation(Connection *conn, Operation *op); @@ -1598,6 +1600,13 @@ connection_threadmain() /* Once we're here we have a pb */ slapi_pblock_get(pb, SLAPI_CONNECTION, &conn); slapi_pblock_get(pb, SLAPI_OPERATION, &op); + + /* Get our plugins ready. */ + if (plugin_v4_start_operation() != PLUGIN_V4_SUCCESS) { + slapi_log_err(SLAPI_LOG_CRIT, "connection_threadmain", "Failed to init plugins for operation\n"); + goto done; + } + maxthreads = config_get_maxthreadsperconn(); more_data = 0; ret = connection_read_operation(conn, op, &tag, &more_data); @@ -1761,6 +1770,15 @@ connection_threadmain() connection_dispatch_operation(conn, op, pb); done: + /* + * Operation is complete, yield the plugin list. + * Future william: Should plugin tls be part of operation instead? + */ + if (plugin_v4_close_operation() != PLUGIN_V4_SUCCESS) { + /* This is a recoverable error, but may leak memory. */ + slapi_log_err(SLAPI_LOG_CRIT, "connection_threadmain", "closing plugin after operation failed!\n"); + } + if (doshutdown) { PR_EnterMonitor(conn->c_mutex); connection_remove_operation_ext(pb, conn, op); diff --git a/ldap/servers/slapd/daemon.c b/ldap/servers/slapd/daemon.c index 4e0466a..890c736 100644 --- a/ldap/servers/slapd/daemon.c +++ b/ldap/servers/slapd/daemon.c @@ -57,6 +57,8 @@ #include #include "fe.h" +#include + #if defined(ENABLE_LDAPI) #include "getsocketpeer.h" #endif /* ENABLE_LDAPI */ @@ -1236,6 +1238,14 @@ slapd_daemon(daemon_ports_t *ports, ns_thrpool_t *tp) plugin_closeall(1 /* Close Backends */, 1 /* Close Globals */); + /* + * Shutdown all plugin_v4 components. + */ + if (plugin_v4_shutdown() != PLUGIN_V4_SUCCESS) { + slapi_log_err(SLAPI_LOG_WARNING, "slapd_daemon", + "Unable to cleanly stop plugin v4 subsystem. Some resources may leak ...\n"); + } + if (!in_referral_mode) { /* Close SNMP collator after the plugins closed... * Replication plugin still performs internal ops that diff --git a/ldap/servers/slapd/dse.c b/ldap/servers/slapd/dse.c index 2634f58..4e21d14 100644 --- a/ldap/servers/slapd/dse.c +++ b/ldap/servers/slapd/dse.c @@ -42,6 +42,8 @@ #include /* provides fsync/close */ +#include + /* #define SLAPI_DSE_DEBUG */ /* define this to force trace log */ /* messages to always be logged */ @@ -1826,6 +1828,10 @@ dse_modify(Slapi_PBlock *pb) /* JCM There should only be one exit point from thi goto done; } } + } else if (slapi_entry_attr_hasvalue(ecc, SLAPI_ATTR_OBJECTCLASS, "nsDylibPlugin4")) { + /* Let the plugin know it's being modified. */ + /* It will work out what actions need to be taken. */ + plugin_v4_modify(ecc); } } } @@ -2036,14 +2042,15 @@ dse_add_plugin(Slapi_Entry *entry, char *returntext) { int rc = LDAP_SUCCESS; - if (!slapi_entry_attr_hasvalue(entry, SLAPI_ATTR_OBJECTCLASS, "nsSlapdPlugin") || - !config_get_dynamic_plugins()) { - /* - * This is not a plugin, or we are not allowing dynamic updates. - */ - return rc; + if (slapi_entry_attr_hasvalue(entry, SLAPI_ATTR_OBJECTCLASS, "nsSlapdPlugin") && + config_get_dynamic_plugins()) { + rc = plugin_add(entry, returntext, 0 /* not locked */); + } else if (slapi_entry_attr_hasvalue(entry, SLAPI_ATTR_OBJECTCLASS, "nsDylibPlugin4")) { + plugin_v4_result result = plugin_v4_add(entry); + if (result != PLUGIN_V4_SUCCESS) { + rc = LDAP_UNWILLING_TO_PERFORM; + } } - rc = plugin_add(entry, returntext, 0 /* not locked */); return rc; } @@ -2056,16 +2063,16 @@ dse_delete_plugin(Slapi_Entry *entry, char *returntext) { int rc = LDAP_SUCCESS; - if (!slapi_entry_attr_hasvalue(entry, SLAPI_ATTR_OBJECTCLASS, "nsSlapdPlugin") || - slapi_entry_attr_hasvalue(entry, "nsslapd-PluginEnabled", "off") || + if (slapi_entry_attr_hasvalue(entry, SLAPI_ATTR_OBJECTCLASS, "nsSlapdPlugin") && + slapi_entry_attr_hasvalue(entry, "nsslapd-PluginEnabled", "off") && !config_get_dynamic_plugins()) { - /* - * This is not a plugin, this plugin was not enabled to begin with, or we - * are not allowing dynamic updates . - */ - return rc; + rc = plugin_delete(entry, returntext, 0 /* not locked */); + } else if (slapi_entry_attr_hasvalue(entry, SLAPI_ATTR_OBJECTCLASS, "nsDylibPlugin4")) { + plugin_v4_result result = plugin_v4_delete(entry); + if (result != PLUGIN_V4_SUCCESS) { + rc = LDAP_UNWILLING_TO_PERFORM; + } } - rc = plugin_delete(entry, returntext, 0 /* not locked */); return rc; } diff --git a/ldap/servers/slapd/fedse.c b/ldap/servers/slapd/fedse.c index f225c40..25146bd1 100644 --- a/ldap/servers/slapd/fedse.c +++ b/ldap/servers/slapd/fedse.c @@ -42,6 +42,7 @@ #include "slap.h" #include "fe.h" #include +#include extern char **getSupportedCiphers(void); extern char **getEnabledCiphers(void); @@ -1522,6 +1523,7 @@ static const char *easter_egg_photos[NUM_EASTER_EGG_PHOTOS + 1]; static struct dse *pfedse = NULL; static int check_plugin_path(Slapi_PBlock *pb, Slapi_Entry *entryBefore, Slapi_Entry *e, int *returncode, char *returntext, void *arg); +static int check_plugin_v4(Slapi_PBlock *pb, Slapi_Entry *entryBefore, Slapi_Entry *e, int *returncode, char *returntext, void *arg); static void internal_add_helper(Slapi_Entry *e, int dont_write_file) @@ -1569,6 +1571,9 @@ init_dse_file(const char *configdir, Slapi_DN *config) dse_register_callback(pfedse, DSE_OPERATION_READ, DSE_FLAG_PREOP, config, LDAP_SCOPE_BASE, "(objectclass=*)", load_config_dse, NULL, NULL); + dse_register_callback(pfedse, DSE_OPERATION_READ, DSE_FLAG_PREOP, config, + LDAP_SCOPE_SUBTREE, "(objectclass=nsDylibPlugin4)", + load_plugin_v4_entry, NULL, NULL); slapi_pblock_set(pb, SLAPI_CONFIG_DIRECTORY, (void *)configdir); /* don't write out the file when reading */ @@ -1848,6 +1853,7 @@ setup_internal_backends(char *configdir) dse_register_callback(pfedse, SLAPI_OPERATION_MODIFY, DSE_FLAG_PREOP, &root, LDAP_SCOPE_BASE, "(objectclass=*)", modify_root_dse, NULL, NULL); dse_register_callback(pfedse, SLAPI_OPERATION_MODIFY, DSE_FLAG_PREOP, &saslmapping, LDAP_SCOPE_SUBTREE, "(objectclass=nsSaslMapping)", sasl_map_config_modify, NULL, NULL); dse_register_callback(pfedse, SLAPI_OPERATION_MODIFY, DSE_FLAG_PREOP, &plugins, LDAP_SCOPE_SUBTREE, "(objectclass=nsSlapdPlugin)", check_plugin_path, NULL, NULL); + dse_register_callback(pfedse, SLAPI_OPERATION_MODIFY, DSE_FLAG_PREOP, &plugins, LDAP_SCOPE_SUBTREE, "(objectclass=nsDylibPlugin4)", check_plugin_v4, NULL, NULL); /* Delete */ dse_register_callback(pfedse, SLAPI_OPERATION_DELETE, DSE_FLAG_PREOP, &config, LDAP_SCOPE_BASE, "(objectclass=*)", dont_allow_that, NULL, NULL); @@ -1864,6 +1870,7 @@ setup_internal_backends(char *configdir) /* Add */ dse_register_callback(pfedse, SLAPI_OPERATION_ADD, DSE_FLAG_PREOP, &saslmapping, LDAP_SCOPE_SUBTREE, "(objectclass=nsSaslMapping)", sasl_map_config_add, NULL, NULL); dse_register_callback(pfedse, SLAPI_OPERATION_ADD, DSE_FLAG_PREOP, &plugins, LDAP_SCOPE_SUBTREE, "(objectclass=nsSlapdPlugin)", check_plugin_path, NULL, NULL); + dse_register_callback(pfedse, SLAPI_OPERATION_ADD, DSE_FLAG_PREOP, &plugins, LDAP_SCOPE_SUBTREE, "(objectclass=nsDylibPlugin4)", check_plugin_v4, NULL, NULL); be = be_new_internal(pfedse, "DSE", DSE_BACKEND, &fedse_plugin); be_addsuffix(be, &root); @@ -2009,3 +2016,32 @@ check_plugin_path(Slapi_PBlock *pb __attribute__((unused)), return rc; } + +static int +check_plugin_v4(Slapi_PBlock *pb __attribute__((unused)), + Slapi_Entry* entryBefore, + Slapi_Entry* e, + int *returncode, + char *returntext, + void *arg __attribute__((unused))) +{ + int rc = SLAPI_DSE_CALLBACK_OK; + + plugin_v4_result result = {0}; + + if (e == NULL) { + /* The add case uses entryBefore */ + result = plugin_v4_validate(entryBefore); + } else { + result = plugin_v4_validate(e); + } + /* Validate logs it's own error message for admin. */ + + if (result != PLUGIN_V4_SUCCESS) { + *returncode = LDAP_UNWILLING_TO_PERFORM; + PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE, "Invalid plugin configuration"); + rc = SLAPI_DSE_CALLBACK_ERROR; + } + return rc; +} + diff --git a/ldap/servers/slapd/libglobs.c b/ldap/servers/slapd/libglobs.c index 1ba3000..bb5c378 100644 --- a/ldap/servers/slapd/libglobs.c +++ b/ldap/servers/slapd/libglobs.c @@ -628,7 +628,7 @@ static struct config_get_and_set NULL, 0, (void **)&global_slapdFrontendConfig.secureport, CONFIG_INT, NULL, NULL}, - {CONFIG_BASEDN_ATTRIBUTE, config_set_basedn, + {CONFIG_BASEDN_ATTRIBUTE, config_set_certmap_basedn, NULL, 0, (void **)&global_slapdFrontendConfig.certmap_basedn, CONFIG_STRING, NULL, NULL /* deletion is not allowed */}, @@ -6090,7 +6090,7 @@ config_get_return_orig_type_switch() } char * -config_get_basedn(void) +config_get_certmap_basedn(void) { slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig(); char *retVal; @@ -6103,7 +6103,7 @@ config_get_basedn(void) } int -config_set_basedn(const char *attrname, char *value, char *errorbuf, int apply) +config_set_certmap_basedn(const char *attrname, char *value, char *errorbuf, int apply) { int retVal = LDAP_SUCCESS; slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig(); diff --git a/ldap/servers/slapd/main.c b/ldap/servers/slapd/main.c index f8b591e..5b3a877 100644 --- a/ldap/servers/slapd/main.c +++ b/ldap/servers/slapd/main.c @@ -53,12 +53,14 @@ union semun #include "slapi-plugin.h" #include "prinit.h" #include "snmp_collator.h" -#include "fe.h" /* client_auth_init() */ +#include "fe.h" #include "protect_db.h" #include "getopt_ext.h" #include "fe.h" #include +#include + #ifdef LINUX /* For mallopt. Should be removed soon. */ #include @@ -790,6 +792,15 @@ main(int argc, char **argv) global_plugin_init(); /* + * Initialise the plugin v4 structures and values. + */ + if (plugin_v4_init() != PLUGIN_V4_SUCCESS) { + slapi_log_err(SLAPI_LOG_EMERG, "main", + "Unable to initialise plugin v4 subsystem\n"); + exit(1); + } + + /* * Process the config files. */ if (0 == slapd_bootstrap_config(slapdFrontendConfig->configdir)) { @@ -1099,6 +1110,17 @@ main(int argc, char **argv) */ task_cleanup(); + /* + * This step checks for any updates and changes on upgrade + * specifically, it manages assumptions about what plugins should exist, and their + * configurations, and potentially even the state of configurations on the server + * and their removal and deprecation. + */ + if (upgrade_server() != UPGRADE_SUCCESS) { + return_value = 1; + goto cleanup; + } + /* init the thread data indexes */ slapi_td_dn_init(); slapi_td_plugin_lock_init(); @@ -1112,6 +1134,10 @@ main(int argc, char **argv) plugin_print_lists(); plugin_startall(argc, argv, NULL /* specific plugin list */); + if (plugin_v4_startall() != PLUGIN_V4_SUCCESS) { + return_value = 1; + goto cleanup; + } compute_plugins_started(); if (housekeeping_start((time_t)0, NULL) == NULL) { return_value = 1; @@ -1180,6 +1206,7 @@ main(int argc, char **argv) reslimit_cleanup(); vattr_cleanup(); sasl_map_done(); + plugin_v4_destroy(); cleanup: compute_terminate(); SSL_ShutdownServerSessionIDCache(); @@ -2971,10 +2998,6 @@ slapd_do_all_nss_ssl_init(int slapd_exemode, int importexport_encrypt, int s_por } } - if (slapd_exemode == SLAPD_EXEMODE_SLAPD) { - client_auth_init(); - } - if (init_ssl && slapd_ssl_init()) { if (force_to_disable_security("SSL", &init_ssl, ports_info)) { return 1; diff --git a/ldap/servers/slapd/operation.c b/ldap/servers/slapd/operation.c index 4a05e0a..e2f836f 100644 --- a/ldap/servers/slapd/operation.c +++ b/ldap/servers/slapd/operation.c @@ -211,6 +211,7 @@ operation_done(Slapi_Operation **op, Connection *conn) factory_destroy_extension(get_operation_object_type(), *op, conn, &((*op)->o_extension)); slapi_sdn_done(&(*op)->o_sdn); slapi_sdn_free(&(*op)->o_target_spec); + (*op)->o_target_spec = NULL; slapi_ch_free_string(&(*op)->o_authtype); if ((*op)->o_searchattrs != NULL) { charray_free((*op)->o_searchattrs); diff --git a/ldap/servers/slapd/pblock.c b/ldap/servers/slapd/pblock.c index 8f87de5..ad47020 100644 --- a/ldap/servers/slapd/pblock.c +++ b/ldap/servers/slapd/pblock.c @@ -3935,6 +3935,9 @@ slapi_pblock_set(Slapi_PBlock *pblock, int arg, void *value) if (pblock->pb_op != NULL) { pblock->pb_op->o_results.r.r_search.nentries = *((int *)value); } + _pblock_assert_pb_intop(pblock); + /* We are given an int, need to extend it */ + pblock->pb_intop->pb_plugin_internal_search_num_entries = (uint64_t)(*((int *)value)); break; /* Referrals encountered while iterating over the result set */ case SLAPI_SEARCH_REFERRALS: @@ -4329,6 +4332,7 @@ slapi_pblock_set_op_stack_elem(Slapi_PBlock *pb, void *stack_elem) pb->pb_intop->op_stack_elem = stack_elem; } + /* * Clear and then set the bind DN and related credentials for the * connection `conn'. diff --git a/ldap/servers/slapd/plugin_v4.c b/ldap/servers/slapd/plugin_v4.c new file mode 100644 index 0000000..eaa9aee --- /dev/null +++ b/ldap/servers/slapd/plugin_v4.c @@ -0,0 +1,1171 @@ +/* BEGIN COPYRIGHT BLOCK + * Copyright (C) 2017 Red Hat, Inc. + * All rights reserved. + * + * License: GPL (version 3 or any later version). + * See LICENSE for details. + * END COPYRIGHT BLOCK */ + +/* See also https://gcc.gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html */ + +/* ========================= WARNING ============================ + * UNLESS YOU HAVE READ: + * https://www.kernel.org/doc/Documentation/memory-barriers.txt + * and SERIOUSLY understand it, and how it works you *MUST* not + * edit this file. This section of the code relies on a deep + * understanding of locking and memory barriers. + * ============================================================== + */ + +/* + * A majority of the safety here is provided by the bptree_cow + * which is already well tested and proven safe. We still need to + * understand it's safety properties, so really, read the above + * before you start making wild changes here. + */ + +#include + +/* + * Provides dlopen and related components. + */ +#include + +/* + * Provides SDS datastructures + */ +#include + +/* For realpath */ +#include +#include + +/** + * Plugin v4 types are *always* dynamic, so there is no need for expensive checks + * in this space. + */ + +typedef enum _plugin_v4_type { + PLUGIN_V4_INTERNAL, + PLUGIN_V4_DYLIB, +} plugin_v4_type; + +typedef enum _plugin_v4_state { + /* Says we are running and enabled. */ + PLUGIN_V4_ENABLED, + /* We are enabled, but not yet running, so wait ... */ + PLUGIN_V4_ENABLED_PRESTART, + /* Not running at all. */ + PLUGIN_V4_DISABLE, +} plugin_v4_state; + +typedef struct _dylib_v4 { + char *path; + void *handle; + uint64_t refcount; +} dylib_v4; + +typedef struct _plugin_v4 { + uint64_t precedence; + char *name; + plugin_v4_state state; + plugin_v4_type type; + dylib_v4 *dl_handle; + void *plugin_private; + uint64_t refcount; + slapi_v4_plugin_result *(*start_fn)(void **ctx); + slapi_v4_plugin_result *(*close_fn)(void **ctx); + slapi_v4_plugin_result *(*bind_certmap_fn)(void *ctx, slapi_v4_certmap_pblock *pbc); +} plugin_v4; + +typedef struct _plugin_key_v4 { + uint64_t precedence; + char *name; +} plugin_key_v4; + +typedef enum _plugin_v4_op_proceed { + OP_PROCEED, + OP_TERMINATE, +} plugin_v4_op_proceed; + +typedef struct _plugin_v4_op_ctx { + plugin_v4_op_proceed proceed; + slapi_v4_plugin_result *res; + void *pb; +} plugin_v4_op_ctx; + +typedef slapi_v4_plugin_result *(*slapi_v4_init_ptr)(slapi_v4_plugin_registration *p_register); + +/* + * We store: + * * tree of dlopened values. These last for the LIFETIME of the server + * because we open them with NODELETE. This means if you delete/add a plugin + * we actually use the same in memory library. This solves *real* consistency + * issues. + * * tree of plugin configs. These are *all* the currently loaded plugin configs + * of the server regardless of enable/disable status. + * * + */ + +static sds_bptree_cow_instance *dylib_tree = NULL; +static sds_bptree_cow_instance *plugin_tree = NULL; + +/* + * plugin thread local storage components + * This is used to keep a thread local map of the active plugins making look up + * and access much faster for a txn. + */ + +typedef enum _plugin_v4_op_state { + PLUGIN_V4_OP_INACTIVE, + PLUGIN_V4_OP_RUNNING, +} plugin_v4_op_state; + +static pthread_key_t plugin_v4_tls_key; + +typedef struct _plugin_v4_tls_map { + plugin_v4_op_state state; + uint64_t txnid; + sds_bptree_transaction *rotxn; + sds_bptree_instance *bind_certmap_map; +} plugin_v4_tls_map; + +/* + * These functions manage the thread local storage. Specifically, the are used + * to destroy the trees and used to + */ + +static void +plugin_v4_tls_destroy(void *tls) { + slapi_log_error(SLAPI_LOG_DEBUG, "plugin_v4_tls_destroy", "cleaning thread local storage\n"); + plugin_v4_tls_map *tls_map = (plugin_v4_tls_map *)tls; + if (tls_map != NULL) { + /* Now destroy all the maps that exist */ + sds_bptree_destroy(tls_map->bind_certmap_map); + /* Finally free the value */ + spal_free(tls_map); + } +} + +/* + * Check registration worked. + */ + +static plugin_v4_result +slapi_v4_plugin_registration_validate(slapi_v4_plugin_registration *pr, plugin_v4 *plugin_inst) { + /* + * For a plugin registration to be valid we require: + * * name + * * precedence + * * start + * * close + * otherwise, we can have as many or few hooks after that. + */ + if (pr->sv4_name == NULL || + pr->precedence == 0 || + pr->sv4_start_fn == NULL || + pr->sv4_close_fn == NULL) { + return PLUGIN_V4_INVALID_PLUGIN; + slapi_log_error(SLAPI_LOG_CRIT, "slapi_v4_plugin_registration_validate", "Plugin returned NULL on a critical pointer type.\n"); + } + /* + * Assert our Cn and name match *exactly* + * + * This is how we force single instance (to a point ...) + */ + if (strcmp(plugin_inst->name, pr->sv4_name) != 0) { + slapi_log_error(SLAPI_LOG_CRIT, "slapi_v4_plugin_registration_validate", "Plugin cn differed from config cn. Plugin 'cn=%s' must be named 'cn=%s'.\n", pr->sv4_name, plugin_inst->name); + return PLUGIN_V4_INVALID_PLUGIN; + } + + return PLUGIN_V4_SUCCESS; +} + +/* Plugin key management. */ + +plugin_key_v4 * +plugin_key_extract(plugin_v4 *p) { + plugin_key_v4 *pk = (plugin_key_v4 *)spal_calloc(sizeof(plugin_key_v4)); + pk->precedence = p->precedence; + pk->name = strdup(p->name); + return pk; +} + +char * +plugin_key_name_extract(plugin_v4 *p) { + return strdup(p->name); +} + +static int64_t +plugin_key_cmp_fn(void *a, void *b) { + /* Convert the types. */ + plugin_key_v4 *pka = (plugin_key_v4 *)a; + plugin_key_v4 *pkb = (plugin_key_v4 *)b; + if (pka->precedence != pkb->precedence) { + if (pka->precedence < pkb->precedence) { + return -1; + } else { + return 1; + } + } + /* Same precedence, strcmp them. */ + return (int64_t)strcmp((const char *)pka->name, (const char *)pkb->name); +} + +void * +plugin_key_dup_fn(void *a) { + plugin_key_v4 *pka = (plugin_key_v4 *)a; + plugin_key_v4 *pkn = (plugin_key_v4 *)spal_calloc(sizeof(plugin_key_v4)); + pkn->precedence = pka->precedence; + pkn->name = strdup(pka->name); + return (void *)pkn; +} + +void +plugin_key_free_fn(void *a) { + plugin_key_v4 *pk = (plugin_key_v4 *)a; + spal_free(pk->name); + spal_free(pk); +} + +/* Plugin instance management */ + +/* ========================= WARNING ============================ + * UNLESS YOU HAVE READ: + * https://www.kernel.org/doc/Documentation/memory-barriers.txt + * and SERIOUSLY understand it, and how it works you *MUST* not + * edit this file. This section of the code relies on a deep + * understanding of locking and memory barriers. + * ============================================================== + */ + +static plugin_v4 * +plugin_create() { + plugin_v4 *p = (plugin_v4 *)spal_calloc(sizeof(plugin_v4)); + __atomic_add_fetch_8(&(p->refcount), 1, __ATOMIC_RELAXED); + return p; +} + +static void * +plugin_dup_fn(void *a) { + /* + * During a dup, we need to rc by 1 + * this is thread safe inside of SDS because in a rotxn /wrtxn + * we already hold at least 1 rc for the node to be alive, so + * this can't fail. + */ + plugin_v4 *p = (plugin_v4 *)a; + __atomic_add_fetch_8(&(p->refcount), 1, __ATOMIC_RELAXED); + return a; +} + +static plugin_v4 * +plugin_clone_fn(plugin_v4 *p1) { + plugin_v4 *p_new = plugin_create(); + p_new->precedence = p1->precedence; + p_new->type = p1->type; + p_new->dl_handle = p1->dl_handle; + p_new->start_fn = p1->start_fn; + p_new->close_fn = p1->close_fn; + p_new->bind_certmap_fn = p1->bind_certmap_fn; + return p_new; +} + +static plugin_v4 * +plugin_from_entry(struct slapi_entry *plugin_entry) { + plugin_v4 *plugin_inst = plugin_create(); + plugin_inst->name = slapi_entry_attr_get_charptr(plugin_entry, "cn"); + + char *state = slapi_entry_attr_get_charptr(plugin_entry, "nsslapd-pluginEnabled"); + if (strcmp(state, "on") == 0) { + plugin_inst->state = PLUGIN_V4_ENABLED_PRESTART; + } else { + plugin_inst->state = PLUGIN_V4_DISABLE; + } + spal_free(state); + plugin_inst->type = PLUGIN_V4_DYLIB; + + return plugin_inst; +} + +static plugin_v4 * +plugin_lookup_from_entry(sds_bptree_transaction *plugin_wrtxn, struct slapi_entry *plugin_entry) { + char *plugin_key = slapi_entry_attr_get_charptr(plugin_entry, "cn"); + if (plugin_key == NULL) { + return NULL; + } + plugin_v4 *plugin_inst = NULL; + sds_bptree_cow_retrieve(plugin_wrtxn, plugin_key, (void **)&plugin_inst); + /* + * If this fails, plugin_inst is already NULL so we have a course of action .... + */ + + spal_free(plugin_key); + + /* return the entry or NULL */ + return plugin_inst; +} + +static void +plugin_free_fn(void *a) { + /* + * Dec the ref count. If we get to 0, there can be no situation + * in which another dup of us occurs, so we can free here. + */ + plugin_v4 *p = (plugin_v4 *)a; + /* + * this can be relaxed, because to have this pointer the barrier + * must have already been issued, so we don't need another. + */ + uint64_t rc = __atomic_sub_fetch_8(&(p->refcount), 1, __ATOMIC_RELAXED); + slapi_log_error(SLAPI_LOG_DEBUG, "plugin_free_fn", "%s %p refcount %"PRId64" \n", p->name, (void *)p, rc); + if (rc == 0) { + /* + * Now we can free it. + */ + slapi_v4_plugin_result *close_result = p->close_fn(&p->plugin_private); + if (close_result == NULL || close_result->result != SLAPI_V4_PLUGIN_SUCCESS) { + /* What was the error? better handle it .... */ + slapi_log_error(SLAPI_LOG_WARNING, "plugin_free_fn", "recoverable - plugin close failed: %"PRId32" '%s'\n", close_result->ldap_code, close_result->msg); + } + slapi_v4_plugin_result_destroy(close_result); + /* + * IF the plugin state is enabled, call the close fn. + */ + spal_free(p->name); + spal_free(p); + } +} + +/* dylib key management */ + +char * +dylib_key_extract(dylib_v4 *d) { + return strdup(d->path); +} + +/* Shared string comparisons */ + +static int64_t +string_key_cmp_fn(void *a, void *b) { + return (int64_t)strcmp((const char *)a, (const char *)b); +} + +static void * +string_key_dup_fn(void *a) { + return (void *)strdup((char *)a); +} + +static void +string_key_free_fn(void *a) { + spal_free(a); +} + +/* dylib helpers */ + +/* ========================= WARNING ============================ + * UNLESS YOU HAVE READ: + * https://www.kernel.org/doc/Documentation/memory-barriers.txt + * and SERIOUSLY understand it, and how it works you *MUST* not + * edit this file. This section of the code relies on a deep + * understanding of locking and memory barriers. + * ============================================================== + */ + +dylib_v4 * +dylib_create() { + dylib_v4 *d = (dylib_v4 *)spal_calloc(sizeof(dylib_v4)); + __atomic_add_fetch_8(&(d->refcount), 1, __ATOMIC_RELAXED); + return d; +} + +void * +dylib_dup_fn(void *a) { + dylib_v4 *d = (dylib_v4 *)a; + __atomic_add_fetch_8(&(d->refcount), 1, __ATOMIC_RELAXED); + return a; +} + +void +dylib_free_fn(void *a) { + dylib_v4 *d = (dylib_v4 *)a; + uint64_t rc = __atomic_sub_fetch_8(&(d->refcount), 1, __ATOMIC_RELAXED); + if (rc == 0) { + slapi_log_error(SLAPI_LOG_DEBUG, "dylib_free_fn", "freeing dylib at rc 0 - %s\n", d->path); + /* Close the library. */ + dlclose(d->handle); + /* Remember, path is from real path which uses malloc, not slapi_ch* - not that this matters too much ... */ + free(d->path); + spal_free(d); + } +} + +/* + * We then get the txn id, and compare to thread local storage. If they match, + * use the TLS cache. If not, we map the fn types, and build TLS of the event + * types. + * + * This means very little inter-thread contention on the lists, they are protected + * by the same txn as we hold from the tree, fast access etc. + */ + +plugin_v4_result +plugin_v4_init() { + /* + * This pattern leaks if we fail to alloc dylib tree, but if we can't + * alloc it, you are so screwed anyway. + */ + if (sds_bptree_cow_init(&plugin_tree, 0, string_key_cmp_fn, plugin_free_fn, plugin_dup_fn, string_key_free_fn, string_key_dup_fn) != SDS_SUCCESS) { + return PLUGIN_V4_RESOURCE; + } + if (sds_bptree_cow_init(&dylib_tree, 0, string_key_cmp_fn, dylib_free_fn, dylib_dup_fn, string_key_free_fn, string_key_dup_fn) != SDS_SUCCESS) { + return PLUGIN_V4_RESOURCE; + } + /* Create the TLS key for threads to store caches of plugin maps */ + if (pthread_key_create(&plugin_v4_tls_key, plugin_v4_tls_destroy) != 0) { + return PLUGIN_V4_RESOURCE; + } + + return PLUGIN_V4_SUCCESS; +} + +/* + * Finally we free and destroy the tree. This is done after the thread + * pools have stopped. + */ +void +plugin_v4_destroy() { + sds_bptree_cow_destroy(plugin_tree); + sds_bptree_cow_destroy(dylib_tree); +} + +static char * +_plugin_v4_normalise_libpath(char *path) { + char *abs_path = NULL; + char *real_path = NULL; + + if (path[0] != '/') { + /* It's not an abs path already */ + /* + 2 for \0 and / */ + size_t total_len = strlen(path) + strlen(PLUGINDIR) + 2; + abs_path = spal_calloc(total_len); + snprintf(abs_path, total_len, "%s/%s", PLUGINDIR, path); + } else { + /* Already abs, just copy it. */ + abs_path = strdup(path); + } + + /* Do we end in .so (or other platform lib nams we could add here) */ + char *suffix = "-plugin.so"; + size_t suffix_len = strlen(suffix); + size_t abs_path_len = strlen(abs_path); + if (strncmp(abs_path + (abs_path_len - suffix_len), suffix, suffix_len) != 0) { + /* Need to add .so.*/ + size_t total_len = abs_path_len + suffix_len + 1; + char *tmp_abs_path = spal_calloc(total_len); + snprintf(tmp_abs_path, total_len, "%s%s", abs_path, suffix); + spal_free(abs_path); + abs_path = tmp_abs_path; + } + + real_path = realpath(abs_path, NULL); + spal_free(abs_path); + + return real_path; +} + +static plugin_v4_result +_plugin_v4_validate_dylib(struct slapi_entry *plugin_entry, dylib_v4 **d) { + /* Normalise the dl path */ + char *path = slapi_entry_attr_get_charptr(plugin_entry, "nsslapd-pluginpath"); + if (path == NULL) { + return PLUGIN_V4_NULL_POINTER; + } + + char *dylib_path = _plugin_v4_normalise_libpath(path); + + slapi_log_error(SLAPI_LOG_DEBUG, "plugin_v4_validate", "normalised to %s\n", dylib_path); + + spal_free(path); + + /* + * IF dylib_path == NULL, then there is NO REAL item in the fs + * that we can access. + */ + if (dylib_path == NULL) { + slapi_log_error(SLAPI_LOG_FATAL, "plugin_v4_validate", "nsslapd-pluginpath: %s does not exist or in inaccesible\n", path); + return PLUGIN_V4_INVALID_CONFIG; + } + + /* + * Does this dylib exist already in our DB? + * We need to do this in a wrtxn because we don't want someone else to try + * and add it while we are looking. + */ + plugin_v4_result final_result = PLUGIN_V4_SUCCESS; + sds_bptree_transaction *dylib_wrtxn; + dylib_v4 *d_tree = NULL; + sds_bptree_cow_wrtxn_begin(dylib_tree, &dylib_wrtxn); + + if (sds_bptree_cow_retrieve(dylib_wrtxn, dylib_path, (void **)&d_tree) == SDS_KEY_PRESENT) { + /* + * It already exists. We don't need to do anything else, we'll + * reuse the handle when it comes to it. + */ + if (d != NULL) { + /* + * provide the handle to our caller. + * normally it's *unsafe* to move out of the txn scope + * but the design of this module dylib tree is additive only + * and we ditch all our refs as we shutdown the plugin trees + * which means moving this out of scope is safe here. + * + * a potential option is to do a dylib dup / free with the plugin? + */ + *d = d_tree; + } + /* AT this point we have succeeded in the check. */ + free(dylib_path); + sds_bptree_cow_wrtxn_abort(&dylib_wrtxn); + } else { + /* It's not in the tree yet. Can we open it? */ + /* Can we open the dylib? */ + void *handle = dlopen(dylib_path, RTLD_NOW|RTLD_LOCAL); + if (handle == NULL) { + sds_bptree_cow_wrtxn_abort(&dylib_wrtxn); + slapi_log_error(SLAPI_LOG_FATAL, "plugin_v4_validate", "Unable to dlopen(%s)\n", dylib_path); + free(dylib_path); + /* okay, we failed here. */ + final_result = PLUGIN_V4_RESOURCE; + } else { + /* We opened the library. Let's put it in the tree. */ + d_tree = dylib_create(); + d_tree->path = dylib_path; + d_tree->handle = handle; + + sds_bptree_cow_insert(dylib_wrtxn, dylib_path, d_tree); + + if (d != NULL) { + *d = d_tree; + } + + sds_bptree_cow_wrtxn_commit(&dylib_wrtxn); + } + } + + return final_result; +} + +plugin_v4_result +plugin_v4_validate(struct slapi_entry *plugin_entry) { + /* Given a plugin entry, validate the configuration. */ + /* + * v3 only checks the dylib works at this point, not the + * actual config. The config is up to the plugin arguably + */ + return _plugin_v4_validate_dylib(plugin_entry, NULL); +} + +/* + * _plugin_v4_create takes a plugin DN and entry, and processes + * it into our plugin tree. + */ +static plugin_v4_result +_plugin_v4_create(sds_bptree_transaction *plugin_wrtxn, struct slapi_entry *plugin_entry, plugin_v4 **plugin_out) { + /* + * Name comes from from_entry. + * validate already confirmed it matches our expectations. + * plugin_inst->name = strdup(p_register->sv4_name); + */ + plugin_v4 *plugin_inst = plugin_from_entry(plugin_entry); + + slapi_v4_init_ptr init_fn_ptr = NULL; + + if (plugin_inst->type == PLUGIN_V4_DYLIB) { + /* First validate the entry */ + dylib_v4 *dl = NULL; + plugin_v4_result result = _plugin_v4_validate_dylib(plugin_entry, &dl); + if (result != PLUGIN_V4_SUCCESS) { + plugin_free_fn((void *)plugin_inst); + return result; + } + /* + * We have a handle to the dl now. Lets check our init function is + * present. + */ + char *init_fn = slapi_entry_attr_get_charptr(plugin_entry, "nsslapd-plugininitfunc"); + if (init_fn == NULL) { + /* Can't init with no fn! */ + plugin_free_fn((void *)plugin_inst); + return PLUGIN_V4_NULL_POINTER; + } + + init_fn_ptr = (slapi_v4_init_ptr)dlsym(dl->handle, init_fn); + spal_free(init_fn); + /* Now stash the dylib lookup */ + plugin_inst->dl_handle = dl; + + } + + if (init_fn_ptr == NULL) { + plugin_free_fn((void *)plugin_inst); + return PLUGIN_V4_INVALID_CONFIG; + } + + /* + * Call the init fn. This is needed to get the fns and precedence. + * this is SAFE to run mulitple times. The wrtxn prevents it being + * run in parallel though. + */ + slapi_v4_plugin_registration *p_register = slapi_v4_plugin_registration_create(); + + slapi_v4_plugin_result *res = init_fn_ptr(p_register); + + if (res->result != SLAPI_V4_PLUGIN_SUCCESS) { + plugin_free_fn((void *)plugin_inst); + slapi_v4_plugin_registration_destroy(p_register); + slapi_log_error(SLAPI_LOG_ERR, "_plugin_v4_create", "Plugin init function failed - %s\n", res->msg); + slapi_v4_plugin_result_destroy(res); + return PLUGIN_V4_INVALID_CONFIG; + } else { + slapi_v4_plugin_result_destroy(res); + } + + + /* + * Validate our plugin registration has enough information. + */ + plugin_v4_result reg_result = slapi_v4_plugin_registration_validate(p_register, plugin_inst); + if (reg_result != PLUGIN_V4_SUCCESS) { + plugin_free_fn((void *)plugin_inst); + slapi_v4_plugin_registration_destroy(p_register); + slapi_log_error(SLAPI_LOG_CRIT, "_plugin_v4_create", "Plugin is registration is invalid, and will not function\n"); + return reg_result; + } + + /* + * Finally, we have enough information now. Lets create the plugin + * object and commit, because we are ready to *rock*. \m/ + */ + plugin_inst->precedence = p_register->precedence; + /* + * For now we only support dylib. + */ + plugin_inst->start_fn = p_register->sv4_start_fn; + plugin_inst->close_fn = p_register->sv4_close_fn; + plugin_inst->bind_certmap_fn = p_register->sv4_bind_certmap_fn; + + /* + * We are done with registration. + */ + slapi_v4_plugin_registration_destroy(p_register); + + /* + * Does this plugin already exist? + * Generate a plugin key, and check. + */ + void *plugin_key = (void*)plugin_key_name_extract(plugin_inst); + sds_result insert_result = sds_bptree_cow_insert(plugin_wrtxn, plugin_key, plugin_inst); + spal_free(plugin_key); + if (insert_result != SDS_SUCCESS) { + plugin_free_fn((void *)plugin_inst); + if (insert_result == SDS_DUPLICATE_KEY) { + /* + * Plugin with this name exists. Need to bail now. Setup *may* try to call this multiple times + * so we just have to ignore it :( + */ + slapi_log_error(SLAPI_LOG_DEBUG, "_plugin_v4_create", "Plugin cn already exists in tree, ignoring ...\n"); + return PLUGIN_V4_DUPLICATE; + } else { + slapi_log_error(SLAPI_LOG_CRIT, "_plugin_v4_create", "Plugin cn failed to be added for an unknown reason.\n"); + return PLUGIN_V4_UNKNOWN; + } + } + + slapi_log_error(SLAPI_LOG_DEBUG, "_plugin_v4_create", "Plugin %s registered correctly\n", plugin_inst->name ); + *plugin_out = plugin_inst; + + return PLUGIN_V4_SUCCESS; +} + +/* + * INTERNAL start and stop. These actually perform the STATE transitions on plugins to move + * them from the "just added" prestart state to enabled or disabled. + */ + +static plugin_v4_result +_plugin_v4_start_trans(plugin_v4 *plugin_inst) { + + /* + * When we get here we should be in PRESTART. Create builds in PRESTART, and add + * does too. If we build "off" we are DISABLE, so we *don't* want to start no + * matter what. + */ + if (plugin_inst->state == PLUGIN_V4_ENABLED || plugin_inst->state == PLUGIN_V4_DISABLE) { + slapi_log_error(SLAPI_LOG_DEBUG, "_plugin_v4_start_trans", "Plugin already enabled or disabled\n"); + return PLUGIN_V4_SUCCESS; + } + + slapi_v4_plugin_result *start_result = plugin_inst->start_fn(&plugin_inst->plugin_private); + + if (start_result == NULL || start_result->result != SLAPI_V4_PLUGIN_SUCCESS) { + /* What was the error? better handle it .... */ + slapi_log_error(SLAPI_LOG_CRIT, "_plugin_v4_start_trans", "plugin start failed: %"PRId32" '%s'\n", start_result->ldap_code, start_result->msg); + return PLUGIN_V4_DUPLICATE; + } else { + plugin_inst->state = PLUGIN_V4_ENABLED; + slapi_log_error(SLAPI_LOG_DEBUG, "_plugin_v4_start_trans", "started %s state %"PRIu32"\n", plugin_inst->name, plugin_inst->state); + } + + slapi_v4_plugin_result_destroy(start_result); + + return PLUGIN_V4_SUCCESS; +} + +static plugin_v4_result +_plugin_v4_close_trans(sds_bptree_transaction *plugin_wrtxn, plugin_v4 *plugin_inst) { + /* + * How does this blackmagic work! Why are you updating and deleting! + * + * So we know that some set of past operations may still be open in the + * tree consuming plugin_inst - so we can *NOT* change it. We need them + * to finish before we call close too. + * + * We exploit the properties of plugin_free_fn, that it calls close + * when the ref count hits 0. So by calling update, this reduces the in + * wrtxn refcount by 1 - so all other references to plugin inst *must* be + * from past generations now. + * + * So either we are the last ref and update will "close" the plugin, or + * we are waiting on older txns - when they close, because we cowed this node, + * the older tree node will be fred, and that will trigger the refcount to drop to + * to 0 as well, which then causes the plugin inst to fall to 0, and the close + * to run. + * + * Lockless thread safety all up in this code mf!!!!! + */ + + plugin_v4_result op_result = PLUGIN_V4_SUCCESS; + + plugin_v4 *plugin_clone = plugin_clone_fn(plugin_inst); + plugin_clone->state = PLUGIN_V4_DISABLE; + + void *plugin_key = (void*)plugin_key_name_extract(plugin_inst); + sds_result update_result = sds_bptree_cow_update(plugin_wrtxn, plugin_key, plugin_clone); + if (update_result != SDS_SUCCESS) { + op_result = PLUGIN_V4_UNKNOWN; + slapi_log_error(SLAPI_LOG_ERR, "_plugin_v4_close_trans", "Failed to disable %s, reverting to enabled.\n", plugin_clone->name); + plugin_free_fn(plugin_clone); + } + string_key_free_fn(plugin_key); + return op_result; +} + +static plugin_v4_result +_plugin_v4_delete_trans(sds_bptree_transaction *plugin_wrtxn, plugin_v4 *plugin_inst) { + /* + * This works on the same principal as close, but uses delete instead. + */ + void *plugin_key = (void*)plugin_key_name_extract(plugin_inst); + sds_result update_result = sds_bptree_cow_delete(plugin_wrtxn, plugin_key); + string_key_free_fn(plugin_key); + if (update_result != SDS_KEY_PRESENT) { + return PLUGIN_V4_UNKNOWN; + } + return PLUGIN_V4_SUCCESS; +} + +static void +plugin_v4_start_cb(void *plugin_key __attribute__((unused)), void *plugin, void *arg __attribute__((unused))) { + plugin_v4 *plugin_inst = (plugin_v4 *)plugin; + _plugin_v4_start_trans(plugin_inst); +} + +/* + * This is the point where everything that's enabled gets moved into the + * actually event proccessing lists. + */ +plugin_v4_result +plugin_v4_startall() { + /* + * For everything in the tree, move the state to enabled from prestart + */ + slapi_log_error(SLAPI_LOG_DEBUG, "plugin_v4_startall", "starting ....\n"); + sds_bptree_transaction *plugin_wrtxn = NULL; + sds_bptree_cow_wrtxn_begin(plugin_tree, &plugin_wrtxn); + + sds_bptree_cow_map(plugin_wrtxn, NULL, plugin_v4_start_cb); + + sds_bptree_cow_wrtxn_commit(&plugin_wrtxn); + slapi_log_error(SLAPI_LOG_DEBUG, "plugin_v4_startall", "started\n"); + return PLUGIN_V4_SUCCESS; +} + +static void +plugin_v4_stop_cb(void *plugin_key __attribute__((unused)), void *plugin, void *arg) { + plugin_v4 *plugin_inst = (plugin_v4 *)plugin; + sds_bptree_transaction *plugin_wrtxn = (sds_bptree_transaction *)arg; + /* + * We *can not* change value or key here, but we can use them to UPDATE + * in the write txn! + */ + _plugin_v4_close_trans(plugin_wrtxn, plugin_inst); +} +/* + * This has mulitple stages. First, we need to let every current user drain + * (hopefully should already be the case). We then clean all the processing lists + * next we ditch all the open plugins configs + * finally, we close everything in the dlopen tree. + */ +plugin_v4_result +plugin_v4_shutdown() { + slapi_log_error(SLAPI_LOG_DEBUG, "plugin_v4_shutdown", "stopping ....\n"); + sds_bptree_transaction *plugin_wrtxn = NULL; + sds_bptree_transaction *plugin_rotxn = NULL; + sds_bptree_cow_wrtxn_begin(plugin_tree, &plugin_wrtxn); + + /* + * Okay, now that we have the wrtxn, we can GUARANTEE no new + * txns will commit after us. So we open a READ txn for the map. + */ + sds_bptree_cow_rotxn_begin(plugin_tree, &plugin_rotxn); + + /* + * Now, we use the rotxn to map the tree because this WILL NOT change + * but within that we pass the wrtxn to the CB to update each key! + */ + sds_bptree_cow_map(plugin_rotxn, plugin_wrtxn, plugin_v4_stop_cb); + + /* + * Now undo in reverse order. + */ + sds_bptree_cow_rotxn_close(&plugin_rotxn); + sds_bptree_cow_wrtxn_commit(&plugin_wrtxn); + slapi_log_error(SLAPI_LOG_DEBUG, "plugin_v4_shutdown", "stopped\n"); + return PLUGIN_V4_SUCCESS; +} + +/* + * When a change to dse is made via cn=config, this picks it up. We know that + * the entry was *modified* only, so we go from there. + */ +plugin_v4_result +plugin_v4_modify(struct slapi_entry *plugin_entry) { + /* + * Update the plugin value, IE replace it. This will effectively call delete then + * add on the plugin. Why? Because this lets the INIT FUNCTION change. + */ + + sds_bptree_transaction *plugin_wrtxn = NULL; + sds_bptree_cow_wrtxn_begin(plugin_tree, &plugin_wrtxn); + + plugin_v4 *plugin_ref = plugin_lookup_from_entry(plugin_wrtxn, plugin_entry); + /* + * IN THEORY this state is impossible, becuase it means we are modify a plugin + * that *is not in the dse*. This *can not happen* but lets be defensive anyway. + */ + if (plugin_ref == NULL) { + sds_bptree_cow_wrtxn_abort(&plugin_wrtxn); + return PLUGIN_V4_UNKNOWN; + } + slapi_log_error(SLAPI_LOG_DEBUG, "plugin_v4_modify", "modifying %s\n", plugin_ref->name); + + plugin_v4_result delete_result = _plugin_v4_delete_trans(plugin_wrtxn, plugin_ref); + if (delete_result != PLUGIN_V4_SUCCESS) { + slapi_log_error(SLAPI_LOG_CRIT, "plugin_v4_modify", "resetting failed, aborting %s\n", plugin_ref->name); + sds_bptree_cow_wrtxn_abort(&plugin_wrtxn); + return delete_result; + } + /* + * May not access plugin ref from here .... + */ + plugin_v4 *plugin_ref_new = NULL; + + plugin_v4_result create_result = _plugin_v4_create(plugin_wrtxn, plugin_entry, &plugin_ref_new); + if (create_result != PLUGIN_V4_SUCCESS) { + slapi_log_error(SLAPI_LOG_CRIT, "plugin_v4_modify", "cow failed, aborting %s\n", plugin_ref_new->name); + sds_bptree_cow_wrtxn_abort(&plugin_wrtxn); + return create_result; + } + + plugin_v4_result enable_result = _plugin_v4_start_trans(plugin_ref_new); + if (enable_result != PLUGIN_V4_SUCCESS) { + slapi_log_error(SLAPI_LOG_CRIT, "plugin_v4_modify", "start failed, aborting %s\n", plugin_ref_new->name); + sds_bptree_cow_wrtxn_abort(&plugin_wrtxn); + } else { + slapi_log_error(SLAPI_LOG_DEBUG, "plugin_v4_modify", "started, commiting %s state : %"PRIu32"\n", plugin_ref->name, plugin_ref_new->state); + sds_bptree_cow_wrtxn_commit(&plugin_wrtxn); + } + + return enable_result; +} + +/* + * This creates the plugin but *does not* start it. + */ +plugin_v4_result +plugin_v4_setup(struct slapi_entry *plugin_entry) { + /* Pelrhaps do this in a txn? */ + /* + * create the txn + */ + sds_bptree_transaction *plugin_wrtxn = NULL; + sds_bptree_cow_wrtxn_begin(plugin_tree, &plugin_wrtxn); + + plugin_v4 *plugin_ref = NULL; + plugin_v4_result create_result = _plugin_v4_create(plugin_wrtxn, plugin_entry, &plugin_ref); + + /* + * Commit or abort. + */ + if (create_result == PLUGIN_V4_SUCCESS) { + /* + * Finally, commit. My plugin is ready. + */ + slapi_log_error(SLAPI_LOG_DEBUG, "plugin_v4_setup", "create commiting %s state: %"PRIu32" \n", plugin_ref->name, plugin_ref->state); + sds_bptree_cow_wrtxn_commit(&plugin_wrtxn); + } else if (create_result == PLUGIN_V4_DUPLICATE) { + slapi_log_error(SLAPI_LOG_DEBUG, "plugin_v4_setup", "create, already exists\n"); + sds_bptree_cow_wrtxn_abort(&plugin_wrtxn); + create_result = PLUGIN_V4_SUCCESS; + } else { + slapi_log_error(SLAPI_LOG_CRIT, "plugin_v4_setup", "create aborting %s \n", plugin_ref->name); + sds_bptree_cow_wrtxn_abort(&plugin_wrtxn); + } + + return create_result; +} + +/* + * This creates the plugin and *does* start it. + */ +plugin_v4_result +plugin_v4_add(struct slapi_entry *plugin_entry) { + slapi_log_error(SLAPI_LOG_DEBUG, "plugin_v4_add", "begin\n"); + /* + * Create the txn + */ + sds_bptree_transaction *plugin_wrtxn = NULL; + sds_bptree_cow_wrtxn_begin(plugin_tree, &plugin_wrtxn); + + plugin_v4 *plugin_ref = NULL; + + plugin_v4_result create_result = _plugin_v4_create(plugin_wrtxn, plugin_entry, &plugin_ref); + if (create_result != PLUGIN_V4_SUCCESS) { + sds_bptree_cow_wrtxn_abort(&plugin_wrtxn); + return create_result; + } + slapi_log_error(SLAPI_LOG_DEBUG, "plugin_v4_add", "adding %s\n", plugin_ref->name); + + plugin_v4_result enable_result = _plugin_v4_start_trans(plugin_ref); + if (enable_result != PLUGIN_V4_SUCCESS) { + slapi_log_error(SLAPI_LOG_CRIT, "plugin_v4_add", "failed to add, aborting %s\n", plugin_ref->name); + sds_bptree_cow_wrtxn_abort(&plugin_wrtxn); + } else { + slapi_log_error(SLAPI_LOG_DEBUG, "plugin_v4_add", "added, commiting %s\n", plugin_ref->name); + sds_bptree_cow_wrtxn_commit(&plugin_wrtxn); + } + return enable_result; +} + +/* + * A plugin was deleted. This means we need to disable it, and then + * let the COW gc do it's job. + */ +plugin_v4_result +plugin_v4_delete(struct slapi_entry *plugin_entry) { + + sds_bptree_transaction *plugin_wrtxn = NULL; + sds_bptree_cow_wrtxn_begin(plugin_tree, &plugin_wrtxn); + + plugin_v4 *plugin_ref = plugin_lookup_from_entry(plugin_wrtxn, plugin_entry); + /* + * IN THEORY this state is impossible, becuase it means we are deleting a plugin + * that *is not in the dse*. This *can not happen* but lets be defensive anyway. + */ + if (plugin_ref == NULL) { + sds_bptree_cow_wrtxn_abort(&plugin_wrtxn); + return PLUGIN_V4_UNKNOWN; + } + slapi_log_error(SLAPI_LOG_DEBUG, "plugin_v4_delete", "removing %s\n", plugin_ref->name); + + plugin_v4_result delete_result = _plugin_v4_delete_trans(plugin_wrtxn, plugin_ref); + if (delete_result != PLUGIN_V4_SUCCESS) { + slapi_log_error(SLAPI_LOG_CRIT, "plugin_v4_delete", "failed to remove, aborting %s\n", plugin_ref->name); + sds_bptree_cow_wrtxn_abort(&plugin_wrtxn); + } else { + slapi_log_error(SLAPI_LOG_DEBUG, "plugin_v4_delete", "removed, commiting %s\n", plugin_ref->name); + sds_bptree_cow_wrtxn_commit(&plugin_wrtxn); + } + /* + * DANGER from here you MUST NOT touch plugin_inst again as it MAY BE FREED. + */ + return delete_result; +} + + +/* + * ===================================================================== + * PLUGIN CALLS HERE + * ===================================================================== + * + * Below this line is where we actually execute plugin callbacks. + * + * The behaviour is that we have a thread local linked list (created by the b+tree) + * of the plugins that are valid for this transaction. This is because our *main* plugin + * tree stores all plugins regardless of state, but the tls bpt stores many smaller + * lists of plugins sorted by PRECEDENCE and NAME, and only adds them if they + * are enabled *and* relevant to the call type. This is why we have many smaller + * maps of plugins here, and we extract them at the start of the operation. + * + * To provide a performant solution, we check if the transaction id we hold + * is the same as the txn id that created the map - if they are we are still valid. + * because txnid is 64bit int, this is never going to conflict. If this valued changes + * we ditch the lists and we build new ones. + * + * The benefit of this is *zero* locking, cache locality, and memory saftey protected + * by the main plugin tree, and we get ordered lists. This makes operation execution + * very fast. + */ + +/* ========================= WARNING ============================ + * UNLESS YOU HAVE READ: + * https://www.kernel.org/doc/Documentation/memory-barriers.txt + * and SERIOUSLY understand it, and how it works you *MUST* not + * edit this file. This section of the code relies on a deep + * understanding of locking and memory barriers. + * ============================================================== + */ + +static void +plugin_v4_bind_certmap_populate_cb(void *key __attribute__((unused)), void *value, void *arg) { + sds_bptree_instance *map = (sds_bptree_instance *)arg; + plugin_v4 *plugin = (plugin_v4 *)value; + slapi_log_error(SLAPI_LOG_DEBUG, "plugin_v4_bind_certmap_populate_cb", "Checking '%s' for state\n", plugin->name); + /* Test if the plugin matches our criteria */ + if (plugin->bind_certmap_fn != NULL && plugin->state == PLUGIN_V4_ENABLED) { + plugin_key_v4 *pkey = plugin_key_extract(plugin); + slapi_log_error(SLAPI_LOG_DEBUG, "plugin_v4_bind_certmap_populate_cb", "Adding '%s' to active map\n", plugin->name); + sds_bptree_insert(map, pkey, plugin); + /* Now free the pkey, bptree clones this. */ + plugin_key_free_fn(pkey); + } else { + slapi_log_error(SLAPI_LOG_DEBUG, "plugin_v4_bind_certmap_populate_cb", "Skipping '%s', either disabled or does not support operation type\n", plugin->name); + } +} + + +plugin_v4_result +plugin_v4_start_operation() { + /* + * This will check the presence of our tls. If it exists + * and is valid, we return it. If it exists and is invalid, we rebuild it. + * if it doesn't existi, we build it. + */ + /* Begin a read transaction */ + sds_bptree_transaction *plugin_rotxn = NULL; + sds_bptree_cow_rotxn_begin(plugin_tree, &plugin_rotxn); + /* extract the txnid */ + uint64_t txnid = sds_bptree_txn_get_id(plugin_rotxn); + /* Get the key */ + plugin_v4_tls_map *tls_map = (plugin_v4_tls_map *)pthread_getspecific(plugin_v4_tls_key); + /* If doesn't exist, create it */ + if (tls_map == NULL) { + tls_map = (plugin_v4_tls_map *)spal_calloc(sizeof(plugin_v4_tls_map)); + tls_map->state = PLUGIN_V4_OP_INACTIVE; + /* + * This is a trick to trigger our update below - since we don't + * match, we'll recreate these lists. + */ + tls_map->txnid = txnid - 1; + pthread_setspecific(plugin_v4_tls_key, (void *)tls_map); + } + if (tls_map->state != PLUGIN_V4_OP_INACTIVE) { + slapi_log_error(SLAPI_LOG_CRIT, "plugin_v4_start_operation", "Invalid state, plugin operation already running\n"); + sds_bptree_cow_rotxn_close(&plugin_rotxn); + return PLUGIN_V4_INVALID_STATE; + } + /* What txn id is it? did we just create? */ + if (tls_map->txnid != txnid) { + /* We differ, so we need to rebuild the txn maps */ + /* Free the existing maps */ + sds_bptree_destroy(tls_map->bind_certmap_map); + /* Now init a new map, and add our plugins that match. */ + sds_bptree_init(&(tls_map->bind_certmap_map), 1, plugin_key_cmp_fn, NULL, plugin_key_free_fn, plugin_key_dup_fn); + /* Now tell our rotxn to map over the values, inserting them to the new bind_certmap_map */ + sds_bptree_cow_map(plugin_rotxn, (void *)tls_map->bind_certmap_map, plugin_v4_bind_certmap_populate_cb); + + /* Now update our txnid */ + tls_map->txnid = txnid; + } + /* Finally stash the rotxn in the tls map for close. */ + tls_map->rotxn = plugin_rotxn; + /* And mark the state as ready. */ + tls_map->state = PLUGIN_V4_OP_RUNNING; + return PLUGIN_V4_SUCCESS; +} + +plugin_v4_result +plugin_v4_close_operation() { + /* Just move the state to done */ + plugin_v4_tls_map *tls_map = (plugin_v4_tls_map *)pthread_getspecific(plugin_v4_tls_key); + if (tls_map == NULL) { + return PLUGIN_V4_INVALID_STATE; + } + tls_map->state = PLUGIN_V4_OP_INACTIVE; + if (sds_bptree_cow_rotxn_close(&(tls_map->rotxn)) != SDS_SUCCESS) { + return PLUGIN_V4_INVALID_STATE; + } + return PLUGIN_V4_SUCCESS; +} + +static void +plugin_v4_call_bind_certmap_cb(void *key __attribute__((unused)), void *value, void *arg) { + plugin_v4_op_ctx *op_ctx = (plugin_v4_op_ctx *)arg; + if (op_ctx->proceed != OP_PROCEED) { + /* A previous operation failed, bail! */ + return; + } + /* Free the previous iterations result */ + slapi_v4_plugin_result_destroy(op_ctx->res); + + plugin_v4 *plugin = (plugin_v4 *)value; + slapi_log_error(SLAPI_LOG_DEBUG, "plugin_v4_call_bind_certmap_cb", "calling bind_certmap function on plugin %s\n", plugin->name); + op_ctx->res = plugin->bind_certmap_fn(plugin->plugin_private, (slapi_v4_certmap_pblock *)op_ctx->pb); + /* Now act on the result. Probably need to hand it to our operation context */ + if (op_ctx->res->result != SLAPI_V4_PLUGIN_SUCCESS) { + slapi_log_error(SLAPI_LOG_WARNING, "plugin_v4_call_bind_certmap_cb", "Plugin %s operation failed reason %s\n", plugin->name, op_ctx->res->msg); + /* In some cases we could send back an ldap result here. */ + op_ctx->proceed = OP_TERMINATE; + } +} + +plugin_v4_result +plugin_v4_call_bind_certmap(slapi_v4_certmap_pblock *pbc) { + slapi_log_error(SLAPI_LOG_DEBUG, "plugin_v4_call_bind_certmap", "calling bind_certmap functions\n"); + /* + * First check the tls + */ + plugin_v4_tls_map *tls_map = (plugin_v4_tls_map *)pthread_getspecific(plugin_v4_tls_key); + if (pbc == NULL || tls_map == NULL || tls_map->state != PLUGIN_V4_OP_RUNNING) { + return PLUGIN_V4_INVALID_STATE; + } + plugin_v4_op_ctx op_ctx = {0}; + op_ctx.proceed = OP_PROCEED; + op_ctx.pb = (void *)pbc; + /* + * Now that we know the TLS is good, access it, and iterate over the bind_certmap_fn types + * We rely on sds map for this + */ + sds_bptree_map(tls_map->bind_certmap_map, &op_ctx, plugin_v4_call_bind_certmap_cb); + /* op_ctx now has a result here */ + /* How can we get a plugin response back out here? Give a ctx? */ + if (op_ctx.res->result == SLAPI_V4_PLUGIN_SUCCESS) { + slapi_v4_plugin_result_destroy(op_ctx.res); + return PLUGIN_V4_SUCCESS; + } else { + slapi_v4_plugin_result_destroy(op_ctx.res); + return PLUGIN_V4_UNKNOWN; + } +} + -- 1.8.3.1