/* * Copyright 2008,2009,2010,2011,2012 Red Hat, Inc. * * 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. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #ifdef HAVE_DIRSRV_SLAPI_PLUGIN_H #include #include #include #else #include #endif #include #include "../yp/yp.h" #ifdef HAVE_TCPD_H #include #endif #include "backend.h" #include "backend.h" #include "back-shr.h" #include "format.h" #include "plugin.h" #include "map.h" #define SCH_CONTAINER_CONFIGURATION_FILTER "(&(" SCH_CONTAINER_CONFIGURATION_GROUP_ATTR "=*)(" SCH_CONTAINER_CONFIGURATION_BASE_ATTR "=*)(" SCH_CONTAINER_CONFIGURATION_FILTER_ATTR "=*)(" SCH_CONTAINER_CONFIGURATION_RDN_ATTR "=*))" /* The data we ask the map cache to keep, for us, for each set. */ struct backend_set_data { struct backend_shr_set_data common; /* Schema compatibility-specific data. */ Slapi_DN *container_sdn; char *rdn_format; char **attribute_format; bool_t check_access; }; struct backend_entry_data { Slapi_DN *original_entry_dn; Slapi_Entry *e; }; /* Read the name of the NIS master. A dummy function for the schema * compatibility plugin. */ void backend_free_master_name(struct plugin_state *state, char *master) { } int backend_read_master_name(struct plugin_state *state, Slapi_PBlock *pb, char **master) { *master = "localhost"; return -1; } /* Manipulate a backend map configuration. */ static void backend_set_config_free_config_contents(void *data) { struct backend_set_data *set_data = data; if (set_data != NULL) { free(set_data->common.group); free(set_data->common.set); free(set_data->common.bases); format_free_attr_list(set_data->common.rel_attrs); free(set_data->common.rel_attr_list); format_free_attr_list(set_data->common.ref_attrs); format_free_inref_attrs(set_data->common.inref_attrs); format_free_ref_attr_list(set_data->common.ref_attr_list); format_free_ref_attr_list(set_data->common.inref_attr_list); free(set_data->common.entry_filter); slapi_sdn_free(&set_data->container_sdn); free(set_data->rdn_format); backend_shr_free_strlist(set_data->attribute_format); } } void backend_set_config_free_config(struct backend_shr_set_data *data) { backend_set_config_free_config_contents(data->self); free(data); } static struct backend_shr_set_data * backend_copy_set_config(const struct backend_set_data *data) { struct backend_set_data *ret; ret = malloc(sizeof(*ret)); if (ret == NULL) { return NULL; } ret->common.self = ret; ret->common.state = data->common.state; ret->common.group = strdup(data->common.group); ret->common.set = strdup(data->common.set); ret->common.bases = backend_shr_dup_strlist(data->common.bases); ret->common.rel_attrs = data->common.rel_attrs ? format_dup_attr_list(data->common.rel_attrs) : NULL; ret->common.rel_attr_list = NULL; ret->common.rel_attrs_list = NULL; ret->common.ref_attrs = data->common.ref_attrs ? format_dup_attr_list(data->common.ref_attrs) : NULL; ret->common.ref_attr_list = data->common.ref_attr_list ? format_dup_ref_attr_list(data->common.ref_attr_list) : NULL; ret->common.inref_attrs = data->common.inref_attrs ? format_dup_inref_attrs(data->common.inref_attrs) : NULL; ret->common.inref_attr_list = data->common.inref_attrs ? format_dup_ref_attr_list(data->common.inref_attr_list) : NULL; ret->common.entry_filter = strdup(data->common.entry_filter); ret->common.skip_uninteresting_updates = data->common.skip_uninteresting_updates; ret->container_sdn = slapi_sdn_dup(data->container_sdn); ret->rdn_format = strdup(data->rdn_format); ret->attribute_format = backend_shr_dup_strlist(data->attribute_format); ret->check_access = data->check_access; if ((ret->common.group == NULL) || (ret->common.set == NULL) || (ret->common.bases == NULL) || (ret->common.entry_filter == NULL) || (ret->container_sdn == NULL)) { backend_set_config_free_config(&ret->common); return NULL; } return &ret->common; } /* Given a configuration entry, read the map configuration for the given group * and container name from the entry. */ void backend_set_config_read_config(struct plugin_state *state, Slapi_Entry *e, const char *group, const char *container, bool_t *flag, struct backend_shr_set_data **pret) { char **bases, *entry_filter, **attributes, *rdn_format, *dn; bool_t check_access; struct backend_set_data ret; Slapi_DN *tmp_sdn; /* Read the values from the configuration entry. */ bases = backend_shr_get_vattr_strlist(state, e, SCH_CONTAINER_CONFIGURATION_BASE_ATTR); entry_filter = backend_shr_get_vattr_filter(state, e, SCH_CONTAINER_CONFIGURATION_FILTER_ATTR); rdn_format = backend_shr_get_vattr_str(state, e, SCH_CONTAINER_CONFIGURATION_RDN_ATTR); check_access = backend_shr_get_vattr_boolean(state, e, SCH_CONTAINER_CONFIGURATION_ACCESS_ATTR, TRUE); attributes = backend_shr_get_vattr_strlist(state, e, SCH_CONTAINER_CONFIGURATION_ATTR_ATTR); /* Populate the returned structure. */ ret.common.state = state; tmp_sdn = slapi_sdn_new_dn_byval(group); ret.common.group = strdup(slapi_sdn_get_ndn(tmp_sdn)); slapi_sdn_free(&tmp_sdn); ret.common.set = strdup(container); ret.common.bases = bases; ret.common.entry_filter = entry_filter; ret.common.rel_attrs = NULL; ret.common.rel_attr_list = NULL; ret.common.rel_attrs_list = NULL; ret.common.ref_attrs = NULL; ret.common.inref_attrs = NULL; ret.common.ref_attr_list = NULL; ret.common.inref_attr_list = NULL; if ((getenv(SCHEMA_COMPAT_PLUGIN_PROCESS_UNINTERESTING_UPDATES_ENV) == NULL) || (atol(getenv(SCHEMA_COMPAT_PLUGIN_PROCESS_UNINTERESTING_UPDATES_ENV)) == 0)) { ret.common.skip_uninteresting_updates = 1; } else { ret.common.skip_uninteresting_updates = 0; } if (strlen(ret.common.set) > 0) { dn = slapi_dn_plus_rdn(ret.common.group, ret.common.set); } else { dn = strdup(ret.common.group); } tmp_sdn = slapi_sdn_new_dn_passin(dn); ret.container_sdn = slapi_sdn_new_dn_byval(slapi_sdn_get_ndn(tmp_sdn)); slapi_sdn_free(&tmp_sdn); ret.rdn_format = rdn_format; ret.attribute_format = attributes; ret.check_access = check_access; *pret = backend_copy_set_config(&ret); free(ret.common.group); free(ret.common.set); backend_shr_free_strlist(ret.common.bases); free(ret.common.entry_filter); slapi_sdn_free(&ret.container_sdn); backend_shr_free_strlist(ret.attribute_format); free(ret.rdn_format); } /* Create and destroy entry-specific data. */ static struct backend_entry_data * backend_entry_make_entry_data(Slapi_DN *original_entry_dn, Slapi_Entry *e) { struct backend_entry_data *ret; ret = malloc(sizeof(*ret)); if (ret != NULL) { ret->original_entry_dn = slapi_sdn_dup(original_entry_dn); ret->e = e; } else { slapi_entry_free(e); } return ret; } static void backend_entry_free_entry_data(void *p) { struct backend_entry_data *data; data = p; slapi_entry_free(data->e); slapi_sdn_free(&data->original_entry_dn); free(data); } /* Retrieve the USN of the passed-in entry, or the last-USN value from the root * DSE, or NULL. The result needs to be free()d. */ static char * backend_entry_get_usn(Slapi_PBlock *pb, Slapi_Entry *e, struct plugin_state *state) { Slapi_Entry *root; Slapi_ValueSet *value_set; Slapi_Value *value; Slapi_DN *sdn; char *attr, *attrs[2], *actual_attr, *val; const char *cval; int count, disposition, buffer_flags, i; root = NULL; if (e != NULL) { /* We'll read "entryUSN" from the entry. */ attr = SLAPI_ATTR_ENTRYUSN; } else { /* We'll read "lastUSN" from the root DSE. */ attr = "lastUSN"; attrs[0] = attr; attrs[1] = NULL; sdn = slapi_sdn_new_dn_byval(""); if (sdn == NULL) { return NULL; } wrap_search_internal_get_entry(pb, sdn, NULL, attrs, &root, state->plugin_desc); slapi_sdn_free(&sdn); e = root; } /* No source entry, and failed to read the root DSE. */ if (e == NULL) { return NULL; } if (slapi_vattr_values_get(e, attr, &value_set, &disposition, &actual_attr, 0, &buffer_flags) != 0) { /* Error reading the attribute. Bail. */ if (root != NULL) { slapi_entry_free(root); } return NULL; } count = slapi_valueset_count(value_set); if (count == 1) { if (slapi_valueset_first_value(value_set, &value) != -1) { cval = slapi_value_get_string(value); } else { cval = NULL; } } else { /* Either no results, or too many results. More likely no * results, if the USN plugin isn't loaded. */ cval = NULL; } val = cval ? strdup(cval) : NULL; slapi_vattr_values_free(&value_set, &actual_attr, buffer_flags); if (root != NULL) { slapi_entry_free(root); } return val; } /* Add operational attributes to a synthetic entry. */ static void backend_set_operational_attributes(Slapi_Entry *e, struct plugin_state *state, time_t timestamp, int n_subordinates, const char *usn) { struct tm timestamp_tm; char timestamp_str[4 + 2 + 2 + 2 + 2 + 2 + 2]; /* YYYYMMDDHHMMSSZ\0 */ /* Set operational attributes. Do it first so that if users of the * plugin want to override the values using the configuration, they * can. */ if (gmtime_r(×tamp, ×tamp_tm) == ×tamp_tm) { sprintf(timestamp_str, "%04d%02d%02d%02d%02d%02dZ", timestamp_tm.tm_year + 1900, timestamp_tm.tm_mon + 1, timestamp_tm.tm_mday, timestamp_tm.tm_hour, timestamp_tm.tm_min, timestamp_tm.tm_sec); slapi_entry_add_string(e, "createTimestamp", timestamp_str); slapi_entry_add_string(e, "modifyTimestamp", timestamp_str); } slapi_entry_add_string(e, "creatorsName", state->plugin_base); slapi_entry_add_string(e, "modifiersName", state->plugin_base); slapi_entry_add_string(e, "entryDN", slapi_entry_get_ndn(e)); if ((usn != NULL) && (strlen(usn) > 0)) { slapi_entry_add_string(e, "entryUSN", usn); } if (n_subordinates > 0) { slapi_entry_add_string(e, "hasSubordinates", "TRUE"); snprintf(timestamp_str, sizeof(timestamp_str), "%ld", (long) n_subordinates); slapi_entry_add_string(e, "numSubordinates", timestamp_str); } } /* Given a map-entry directory entry, determine a key, a value, and extra data * to be stored in the map cache, and add them to the map cache. */ void backend_set_entry(Slapi_PBlock *pb, Slapi_Entry *e, struct backend_set_data *data) { const char *hexchars = "0123456789ABCDEF"; char *rdn, *ndn, *ldif, *plugin_id, *keys[2], *values[2], **ava, *p, *q; char *usn, *attr, *val; unsigned int rdn_len, value_len, *ava_lens; const char *rdnstr; int len, i, j, k, count; Slapi_Entry *entry; Slapi_DN *e_dn, *sdn; Slapi_RDN *srdn; Slapi_Value **value; plugin_id = data->common.state->plugin_desc->spd_id; e_dn = slapi_entry_get_sdn(e); ndn = slapi_entry_get_ndn(e); if (ndn != NULL) { slapi_log_error(SLAPI_LOG_PLUGIN, plugin_id, "examining input entry \"%s\"\n", ndn); } else { slapi_log_error(SLAPI_LOG_PLUGIN, plugin_id, "examining unnamed input entry(?)\n"); } /* Generate the RDN for the entry. */ rdn = format_get_data(data->common.state, pb, e, data->common.group, data->common.set, data->rdn_format, NULL, &data->common.rel_attrs, &data->common.ref_attrs, &data->common.inref_attrs, &data->common.ref_attr_list, &data->common.inref_attr_list, &rdn_len); if ((rdn == NULL) || (strlen(rdn) == 0) || (strchr(rdn, '=') == NULL)) { slapi_log_error(SLAPI_LOG_FATAL, plugin_id, "no RDN for %s, unsetting domain/map/id " "\"%s\"/\"%s\"/(\"%s\")\n", ndn, data->common.group, data->common.set, ndn); map_data_unset_entry(data->common.state, data->common.group, data->common.set, ndn); return; } /* Assume attribute=value and hex-escape the whole value to build the * new entry's RDN. The server functions will un-escape whatever they * can when we build the resulting DN. */ q = malloc(strlen(rdn) * 3 + 1); p = strchr(rdn, '=') + 1; i = p - rdn; memcpy(q, rdn, i); while (*p != '\0') { j = ((unsigned int) *p++) & 0xff; q[i++] = '\\'; q[i++] = hexchars[(j & 0xf0) >> 4]; q[i++] = hexchars[j & 0xf]; } q[i] = '\0'; srdn = slapi_rdn_new_dn(q); free(q); /* Now build the SDN. Check it for validity. */ sdn = slapi_sdn_add_rdn(slapi_sdn_dup(data->container_sdn), srdn); slapi_rdn_free(&srdn); if ((sdn == NULL) || (slapi_sdn_get_dn(sdn) == NULL) || (slapi_sdn_get_ndn(sdn) == NULL)) { slapi_log_error(SLAPI_LOG_FATAL, plugin_id, "would generate an invalid DN (1), " "unsetting domain/map/id " "\"%s\"/\"%s\"/(\"%s\")\n", data->common.group, data->common.set, ndn); map_data_unset_entry(data->common.state, data->common.group, data->common.set, ndn); if (sdn != NULL) { slapi_sdn_free(&sdn); } format_free_data(rdn); return; } /* Now build the entry itself. Set the DN first, and make sure it took * the value. */ entry = slapi_entry_alloc(); slapi_entry_set_sdn(entry, sdn); slapi_sdn_free(&sdn); if ((slapi_entry_get_dn(entry) == NULL) || (slapi_entry_get_ndn(entry) == NULL)) { slapi_log_error(SLAPI_LOG_FATAL, plugin_id, "would generate an invalid DN (2), " "unsetting domain/map/id " "\"%s\"/\"%s\"/(\"%s\")\n", data->common.group, data->common.set, ndn); map_data_unset_entry(data->common.state, data->common.group, data->common.set, ndn); slapi_entry_free(entry); format_free_data(rdn); return; } /* Set operational attributes here so that they can be overridden. */ usn = backend_entry_get_usn(pb, e, data->common.state); backend_set_operational_attributes(entry, data->common.state, time(NULL), 0, usn); free(usn); /* Iterate through the set of attributes. */ if (data->attribute_format != NULL) { for (i = 0; data->attribute_format[i] != NULL; i++) { /* Expand the format specifier into a list. */ ava_lens = NULL; ava = format_get_data_set(data->common.state, pb, e, data->common.group, data->common.set, data->attribute_format[i], NULL, &data->common.rel_attrs, &data->common.ref_attrs, &data->common.inref_attrs, &data->common.ref_attr_list, &data->common.inref_attr_list, &ava_lens); if ((ava != NULL) && (ava_lens != NULL)) { /* Count the values. */ count = 0; for (j = 0; ava[j] != NULL; j++) { count++; } /* Create the value array. */ value = malloc((count + 1) * sizeof(Slapi_Value *)); if (value != NULL) { attr = NULL; len = 0; k = 0; for (j = 0; ava[j] != NULL; j++) { /* Assume attribute=value. */ val = memchr(ava[j], '=', ava_lens[j]); /* Skip over anything that didn't have * a '=' or that produced an empty * value. */ if ((val != NULL) && (ava_lens[j] > val + 1 - ava[j])) { /* Add a new value. */ value[k] = slapi_value_new(); if (value[k] != NULL) { /* Set the value. */ attr = ava[j]; len = ava_lens[j]; slapi_value_set(value[k], val + 1, ava_lens[j] - (val + 1 - ava[j])); k++; } } } value[k] = NULL; if ((k > 0) && (attr != NULL) && (len > 0)) { /* We assumed attribute=value when we * saved this particular value. * Pull the attribute name out * of the last attribute=value * pair that we examined. */ val = memchr(attr, '=', len); if (val != NULL) { *val = '\0'; slapi_entry_merge_values_sv(entry, attr, value); *val = '='; } } /* Clean up the values. */ for (j = 0; j < k; j++) { slapi_value_free(&value[j]); } free(value); } } format_free_data_set(ava, ava_lens); } } /* Try to make the entry look "right". */ if (!slapi_entry_rdn_values_present(entry)) { slapi_entry_add_rdn_values(entry); } if (slapi_entry_schema_check(NULL, entry) != 0) { slapi_entry_add_string(entry, "objectClass", "extensibleObject"); } /* Clean up the entry by doing a round trip through the LDIF parser. */ ldif = slapi_entry2str(entry, &len); slapi_entry_free(entry); entry = slapi_str2entry(ldif, SLAPI_STR2ENTRY_REMOVEDUPVALS | SLAPI_STR2ENTRY_ADDRDNVALS | SLAPI_STR2ENTRY_EXPAND_OBJECTCLASSES | SLAPI_STR2ENTRY_NOT_WELL_FORMED_LDIF); slapi_ch_free((void **) &ldif); /* Normalize the RDN, so that we can use it as a key. */ srdn = slapi_rdn_new_sdn(slapi_entry_get_sdn(entry)); if (srdn != NULL) { rdnstr = slapi_rdn_get_rdn(srdn); } else { rdnstr = NULL; } /* If we actually generated a useful new entry for this entry, then set * it, otherwise clear it in case there was one set before. */ if ((rdnstr != NULL) && (slapi_entry_get_ndn(entry) != NULL)) { slapi_log_error(SLAPI_LOG_PLUGIN, plugin_id, "setting group/container/key/value " "\"%s\"/\"%s\"/\"%s\"(\"%s\")=\"%s\"\n", data->common.group, data->common.set, rdn, ndn, slapi_entry_get_ndn(entry)); keys[0] = (char *) rdnstr; keys[1] = NULL; values[0] = (char *) slapi_entry_get_ndn(entry); values[1] = NULL; value_len = -1; map_data_set_entry(data->common.state, data->common.group, data->common.set, ndn, &rdn_len, keys, &value_len, values, backend_entry_make_entry_data(e_dn, entry), backend_entry_free_entry_data); } else { if (rdnstr == NULL) { slapi_log_error(SLAPI_LOG_FATAL, plugin_id, "would generate an invalid RDN, " "unsetting domain/map/id " "\"%s\"/\"%s\"/(\"%s\")\n", data->common.group, data->common.set, ndn); } if (slapi_entry_get_ndn(entry) == NULL) { slapi_log_error(SLAPI_LOG_FATAL, plugin_id, "would generate an invalid entry DN, " "unsetting domain/map/id " "\"%s\"/\"%s\"/(\"%s\")\n", data->common.group, data->common.set, ndn); } slapi_log_error(SLAPI_LOG_PLUGIN, plugin_id, "no value for %s, unsetting domain/map/id " "\"%s\"/\"%s\"/(\"%s\")\n", ndn, data->common.group, data->common.set, ndn); map_data_unset_entry(data->common.state, data->common.group, data->common.set, ndn); slapi_entry_free(entry); } slapi_rdn_free(&srdn); format_free_data(rdn); } /* Process a set configuration directory entry. Pull out the group and * container names which are valid for this configuration and configure such a * container for each in turn. */ int backend_set_config_entry_add_cb(Slapi_Entry *e, void *callback_data) { char **groups, **containers; int i, j; struct backend_set_config_entry_add_cbdata *cbdata; cbdata = callback_data; groups = backend_shr_get_vattr_strlist(cbdata->state, e, SCH_CONTAINER_CONFIGURATION_GROUP_ATTR); containers = backend_shr_get_vattr_strlist(cbdata->state, e, SCH_CONTAINER_CONFIGURATION_CONTAINER_ATTR); for (i = 0; (groups != NULL) && (groups[i] != NULL); i++) { /* If this is a multiple-container group, walk the list. */ for (j = 0; (containers != NULL) && (containers[j] != NULL); j++) { backend_shr_set_config_entry_add(cbdata->state, cbdata->pb, e, groups[i], containers[j]); } /* If there are no containers, add one with an empty name. */ if (containers == NULL) { backend_shr_set_config_entry_add(cbdata->state, cbdata->pb, e, groups[i], ""); } } backend_shr_free_strlist(containers); backend_shr_free_strlist(groups); return 0; } /* Process a set configuration directory entry. Pull out the domain and map * names which are specified in the entry and delete each in turn. */ int backend_set_config_entry_delete_cb(Slapi_Entry *e, void *callback_data) { struct plugin_state *state; state = callback_data; return backend_shr_set_config_entry_delete(state, e, SCH_CONTAINER_CONFIGURATION_GROUP_ATTR, SCH_CONTAINER_CONFIGURATION_CONTAINER_ATTR); } /* Functions for passing information about a container's configuration to the * formatting functions. */ struct backend_get_set_config_if_matching_cb { struct plugin_state *state; Slapi_DN *groupdn, *setrdn; Slapi_DN *search_groupdn, *search_setrdn; char **bases; char *entry_filter; }; void backend_free_set_config(char **bases, char *entry_filter) { backend_shr_free_strlist(bases); free(entry_filter); } static bool_t backend_get_set_config_entry_if_matching_cb(Slapi_Entry *e, void *callback_data) { struct backend_get_set_config_if_matching_cb *cbdata; char **groups, **sets; int i, j; cbdata = callback_data; groups = backend_shr_get_vattr_strlist(cbdata->state, e, SCH_CONTAINER_CONFIGURATION_GROUP_ATTR); sets = backend_shr_get_vattr_strlist(cbdata->state, e, SCH_CONTAINER_CONFIGURATION_CONTAINER_ATTR); if (groups == NULL) { backend_shr_free_strlist(groups); backend_shr_free_strlist(sets); return TRUE; } for (i = 0; (groups[i] != NULL); i++) { cbdata->groupdn = slapi_sdn_set_dn_byval(cbdata->groupdn, groups[i]); for (j = 0; (sets != NULL) && (sets[j] != NULL); j++) { cbdata->setrdn = slapi_sdn_set_dn_byval(cbdata->setrdn, sets[j]); if ((slapi_sdn_compare(cbdata->groupdn, cbdata->search_groupdn) == 0) && (slapi_sdn_compare(cbdata->setrdn, cbdata->search_setrdn) == 0)) { slapi_log_error(SLAPI_LOG_PLUGIN, cbdata->state->plugin_desc->spd_id, "reading container configuration from \"%s\"\n", slapi_entry_get_ndn(e)); cbdata->bases = backend_shr_get_vattr_strlist(cbdata->state, e, SCH_CONTAINER_CONFIGURATION_BASE_ATTR); cbdata->entry_filter = backend_shr_get_vattr_filter(cbdata->state, e, SCH_CONTAINER_CONFIGURATION_FILTER_ATTR); } } if (sets == NULL) { if (slapi_sdn_compare(cbdata->groupdn, cbdata->search_groupdn) == 0) { slapi_log_error(SLAPI_LOG_PLUGIN, cbdata->state->plugin_desc->spd_id, "reading container configuration from \"%s\"\n", slapi_entry_get_ndn(e)); cbdata->bases = backend_shr_get_vattr_strlist(cbdata->state, e, SCH_CONTAINER_CONFIGURATION_BASE_ATTR); cbdata->entry_filter = backend_shr_get_vattr_filter(cbdata->state, e, SCH_CONTAINER_CONFIGURATION_FILTER_ATTR); } } } backend_shr_free_strlist(groups); backend_shr_free_strlist(sets); return TRUE; } void backend_get_set_config(Slapi_PBlock *parent_pb, struct plugin_state *state, const char *group, const char *container, char ***bases, char **entry_filter) { Slapi_PBlock *pb; char *attrs[] = {SCH_CONTAINER_CONFIGURATION_FILTER_ATTR, SCH_CONTAINER_CONFIGURATION_BASE_ATTR, NULL}; Slapi_DN *groupdn, *setrdn; struct backend_get_set_config_if_matching_cb cbdata; /* Build the search filter. */ groupdn = slapi_sdn_new_dn_byval(group); if (groupdn == NULL) { slapi_log_error(SLAPI_LOG_PLUGIN, state->plugin_desc->spd_id, "out of memory reading configuration for " "\"%s\"/\"%s\"!\n", group, container); return; } if (strlen(container) > 0) { setrdn = slapi_sdn_new_dn_byval(container); if (setrdn == NULL) { slapi_sdn_free(&groupdn); slapi_log_error(SLAPI_LOG_PLUGIN, state->plugin_desc->spd_id, "out of memory reading configuration " "for \"%s\"/\"%s\"!\n", group, container); return; } } else { setrdn = NULL; } cbdata.groupdn = slapi_sdn_new(); if (cbdata.groupdn == NULL) { if (setrdn != NULL) { slapi_sdn_free(&setrdn); } slapi_sdn_free(&groupdn); slapi_log_error(SLAPI_LOG_PLUGIN, state->plugin_desc->spd_id, "out of memory reading configuration for " "\"%s\"/\"%s\"!\n", group, container); return; } cbdata.setrdn = slapi_sdn_new(); if (cbdata.setrdn == NULL) { slapi_sdn_free(&cbdata.groupdn); if (setrdn != NULL) { slapi_sdn_free(&setrdn); } slapi_sdn_free(&groupdn); slapi_log_error(SLAPI_LOG_PLUGIN, state->plugin_desc->spd_id, "out of memory reading configuration for " "\"%s\"/\"%s\"!\n", group, container); return; } /* Perform the search. */ slapi_log_error(SLAPI_LOG_PLUGIN, state->plugin_desc->spd_id, "searching from \"%s\" for \"%s\" for configuration\n", state->plugin_base, SCH_CONTAINER_CONFIGURATION_FILTER); pb = wrap_pblock_new(parent_pb); slapi_search_internal_set_pb(pb, state->plugin_base, LDAP_SCOPE_SUBTREE, SCH_CONTAINER_CONFIGURATION_FILTER, attrs, FALSE, NULL, NULL, state->plugin_identity, 0); cbdata.bases = NULL; cbdata.state = state; cbdata.entry_filter = NULL; cbdata.search_groupdn = groupdn; cbdata.search_setrdn = setrdn; slapi_search_internal_callback_pb(pb, &cbdata, NULL, backend_get_set_config_entry_if_matching_cb, NULL); slapi_pblock_destroy(pb); /* Return the results. */ *bases = cbdata.bases; *entry_filter = cbdata.entry_filter; /* Clean up. */ slapi_sdn_free(&cbdata.setrdn); slapi_sdn_free(&cbdata.groupdn); if (setrdn != NULL) { slapi_sdn_free(&setrdn); } slapi_sdn_free(&groupdn); } /* Given an entry, return the filter which will match a container entry beneath * the plugin's configuration entry. */ const char * backend_entry_get_set_config_entry_filter(void) { return SCH_CONTAINER_CONFIGURATION_FILTER; } /* Re-read plugin-wide settings that may have changed. Nothing to do. */ void backend_update_params(Slapi_PBlock *pb, struct plugin_state *state) { } /* Intercept a search request, and if it belongs to one of our compatibility * trees, answer from our cache before letting the default database have a * crack at it. */ struct backend_search_cbdata { Slapi_PBlock *pb; struct plugin_state *state; char *target, *strfilter, **attrs; int scope, sizelimit, timelimit, attrsonly; bool_t check_access; Slapi_DN *target_dn; Slapi_Filter *filter; bool_t answer; int result; bool_t matched; char *closest_match, *text; int n_entries; }; static bool_t backend_should_descend(Slapi_DN *this_dn, Slapi_DN *target_dn, int scope) { switch (scope) { case LDAP_SCOPE_BASE: /* The target DN needs to be a subordinate of this entry, but * not actually be the entry itself. */ if ((slapi_sdn_issuffix(target_dn, this_dn) != 0) && (slapi_sdn_compare(target_dn, this_dn) != 0)) { return TRUE; } break; case LDAP_SCOPE_ONELEVEL: /* The target DN needs to be a subordinate of this entry, or * be the entry itself. */ if (slapi_sdn_issuffix(target_dn, this_dn) != 0) { return TRUE; } break; case LDAP_SCOPE_SUBTREE: /* The target DN needs to be a subordinate of this entry, or * this entry needs to be a subordinate of the target. */ if ((slapi_sdn_issuffix(target_dn, this_dn) != 0) || (slapi_sdn_issuffix(this_dn, target_dn) != 0)) { return TRUE; } break; default: break; } return FALSE; } static bool_t backend_search_entry_cb(const char *domain, const char *map, bool_t secure, const char *key, unsigned int key_len, const char *value, unsigned int value_len, const char *id, int key_index, void *backend_data, void *cb_data) { Slapi_DN *sdn; struct backend_search_cbdata *cbdata; struct backend_entry_data *entry_data; int result; cbdata = cb_data; entry_data = backend_data; sdn = slapi_entry_get_sdn(entry_data->e); /* Check if this entry _is_ the target. */ if (slapi_sdn_compare(sdn, cbdata->target_dn) == 0) { cbdata->matched = TRUE; } /* Check if this entry belongs. */ if (slapi_sdn_scope_test(sdn, cbdata->target_dn, cbdata->scope) == 0) { /* If the target DN would have been a subordinate of this * entry, store its DN as the closest match. */ if ((slapi_sdn_issuffix(cbdata->target_dn, sdn) != 0) && !cbdata->matched) { free(cbdata->closest_match); cbdata->closest_match = strdup(slapi_sdn_get_ndn(sdn)); } return TRUE; } /* Now check the entry against the filter. */ result = slapi_filter_test(cbdata->pb, entry_data->e, cbdata->filter, cbdata->check_access); switch (result) { default: /* Not a match. */ break; case 0: /* Match. Return the entry. */ slapi_log_error(SLAPI_LOG_PLUGIN, cbdata->state->plugin_desc->spd_id, "search matched %s\n", slapi_sdn_get_ndn(sdn)); slapi_send_ldap_search_entry(cbdata->pb, entry_data->e, NULL, cbdata->attrs, cbdata->attrsonly); cbdata->n_entries++; break; } return TRUE; } static bool_t backend_search_set_cb(const char *group, const char *set, bool_t flag, void *backend_data, void *cb_data) { struct backend_search_cbdata *cbdata; struct backend_set_data *set_data; Slapi_Entry *set_entry; int result, n_entries; const char *ndn; cbdata = cb_data; set_data = backend_data; cbdata->check_access = set_data->check_access; /* Check the set itself, unless it's also the group, in which case we * already evaluated it for this search. */ if ((strlen(set_data->common.set) != 0) && (slapi_sdn_scope_test(set_data->container_sdn, cbdata->target_dn, cbdata->scope))) { set_entry = slapi_entry_alloc(); slapi_entry_add_string(set_entry, "objectClass", "extensibleObject"); slapi_entry_set_sdn(set_entry, set_data->container_sdn); n_entries = map_data_get_map_size(cbdata->state, set_data->common.group, set_data->common.set); backend_set_operational_attributes(set_entry, cbdata->state, time(NULL), n_entries, NULL); if (!slapi_entry_rdn_values_present(set_entry)) { slapi_entry_add_rdn_values(set_entry); } ndn = slapi_sdn_get_ndn(set_data->container_sdn); result = slapi_filter_test(cbdata->pb, set_entry, cbdata->filter, cbdata->check_access); switch (result) { default: /* Not a match. */ break; case 0: /* Match. Return the entry. */ slapi_log_error(SLAPI_LOG_PLUGIN, cbdata->state->plugin_desc->spd_id, "search matched %s\n", ndn); slapi_send_ldap_search_entry(cbdata->pb, set_entry, NULL, cbdata->attrs, cbdata->attrsonly); cbdata->n_entries++; break; } slapi_entry_free(set_entry); } /* Check if this set _is_ the target. */ if (slapi_sdn_compare(set_data->container_sdn, cbdata->target_dn) == 0) { cbdata->matched = TRUE; } /* Walk the set of entries in this set if they're in scope. */ if (backend_should_descend(set_data->container_sdn, cbdata->target_dn, cbdata->scope)) { map_data_foreach_entry_id(cbdata->state, group, set, NULL, backend_search_entry_cb, cbdata); } /* If we didn't find an exact match for the entry, then store this * container's DN as the closest match. */ if ((!cbdata->matched) && (cbdata->closest_match == NULL) && slapi_sdn_issuffix(cbdata->target_dn, set_data->container_sdn)) { ndn = slapi_sdn_get_ndn(set_data->container_sdn); cbdata->closest_match = strdup(ndn); } return TRUE; } static bool_t backend_search_group_cb(const char *group, void *cb_data) { struct backend_search_cbdata *cbdata; Slapi_DN *group_dn; Slapi_Entry *group_entry; int result, n_maps; cbdata = cb_data; /* Check the group itself. */ group_dn = slapi_sdn_new_dn_byval(group); if (slapi_sdn_scope_test(group_dn, cbdata->target_dn, cbdata->scope)) { group_entry = slapi_entry_alloc(); slapi_entry_add_string(group_entry, "objectClass", "extensibleObject"); slapi_entry_set_sdn(group_entry, group_dn); n_maps = map_data_get_domain_size(cbdata->state, group); backend_set_operational_attributes(group_entry, cbdata->state, time(NULL), n_maps, NULL); if (!slapi_entry_rdn_values_present(group_entry)) { slapi_entry_add_rdn_values(group_entry); } result = slapi_filter_test(cbdata->pb, group_entry, cbdata->filter, cbdata->check_access); switch (result) { default: /* Not a match. */ break; case 0: /* Match. Return the entry. */ slapi_log_error(SLAPI_LOG_PLUGIN, cbdata->state->plugin_desc->spd_id, "search matched %s\n", group); slapi_send_ldap_search_entry(cbdata->pb, group_entry, NULL, cbdata->attrs, cbdata->attrsonly); cbdata->n_entries++; break; } slapi_entry_free(group_entry); } /* Check if this group _is_ the target. */ if (slapi_sdn_compare(group_dn, cbdata->target_dn) == 0) { cbdata->matched = TRUE; } /* Now check the group's sets and their contents if they're in scope. */ if (backend_should_descend(group_dn, cbdata->target_dn, cbdata->scope)) { map_data_foreach_map(cbdata->state, group, backend_search_set_cb, cbdata); } /* If we didn't find an exact match for the entry, then store this * group's DN as the closest match. */ if ((!cbdata->matched) && (cbdata->closest_match == NULL) && slapi_sdn_issuffix(cbdata->target_dn, group_dn)) { cbdata->closest_match = strdup(slapi_sdn_get_ndn(group_dn)); } /* If the search is confined to this group, we need to send the result * ourselves. */ if (slapi_sdn_scope_test(cbdata->target_dn, group_dn, LDAP_SCOPE_SUBTREE)) { cbdata->answer = TRUE; } slapi_sdn_free(&group_dn); return TRUE; } static const char * backend_sch_scope_as_string(int scope) { switch (scope) { case LDAP_SCOPE_SUBTREE: return " (sub)"; break; case LDAP_SCOPE_ONELEVEL: return " (one)"; break; case LDAP_SCOPE_BASE: return " (base)"; break; #ifdef LDAP_SCOPE_SUBORDINATE case LDAP_SCOPE_SUBORDINATE: return " (children)"; break; #endif } return ""; } static int backend_search_cb(Slapi_PBlock *pb) { struct backend_search_cbdata cbdata; if (wrap_get_call_level() > 0) { return 0; } memset(&cbdata, 0, sizeof(cbdata)); cbdata.pb = pb; slapi_pblock_get(pb, SLAPI_PLUGIN_PRIVATE, &cbdata.state); if (cbdata.state->plugin_base == NULL) { /* The plugin was not actually started. */ return 0; } slapi_pblock_get(pb, SLAPI_SEARCH_TARGET, &cbdata.target); slapi_pblock_get(pb, SLAPI_SEARCH_SCOPE, &cbdata.scope); slapi_pblock_get(pb, SLAPI_SEARCH_SIZELIMIT, &cbdata.sizelimit); slapi_pblock_get(pb, SLAPI_SEARCH_TIMELIMIT, &cbdata.timelimit); slapi_pblock_get(pb, SLAPI_SEARCH_FILTER, &cbdata.filter); slapi_pblock_get(pb, SLAPI_SEARCH_STRFILTER, &cbdata.strfilter); slapi_pblock_get(pb, SLAPI_SEARCH_ATTRS, &cbdata.attrs); slapi_pblock_get(pb, SLAPI_SEARCH_ATTRSONLY, &cbdata.attrsonly); cbdata.answer = FALSE; cbdata.result = 0; cbdata.matched = FALSE; cbdata.closest_match = NULL; cbdata.text = NULL; cbdata.n_entries = 0; /* Okay, we can search. */ slapi_log_error(SLAPI_LOG_PLUGIN, cbdata.state->plugin_desc->spd_id, "searching from \"%s\" for \"%s\" with scope %d%s\n", cbdata.target, cbdata.strfilter, cbdata.scope, backend_sch_scope_as_string(cbdata.scope)); cbdata.target_dn = slapi_sdn_new_dn_byval(cbdata.target); /* Check if there's a backend handling this search. */ if (!slapi_be_exist(cbdata.target_dn)) { slapi_log_error(SLAPI_LOG_PLUGIN, cbdata.state->plugin_desc->spd_id, "slapi_be_exists(\"%s\") = 0, ignoring search\n", cbdata.target); slapi_sdn_free(&cbdata.target_dn); return 0; } /* Walk the list of groups. */ wrap_inc_call_level(); map_rdlock(); map_data_foreach_domain(cbdata.state, backend_search_group_cb, &cbdata); map_unlock(); wrap_dec_call_level(); /* If we "own" the search target DN, then we need to send a response. */ if (cbdata.answer) { if (cbdata.matched || (cbdata.n_entries > 0)) { /* Just in case, free the closest-match that we've * recorded. */ free(cbdata.closest_match); cbdata.closest_match = NULL; } else { /* Return a no-such-object error because the target DN * was not found. */ cbdata.result = LDAP_NO_SUCH_OBJECT; } slapi_log_error(SLAPI_LOG_PLUGIN, cbdata.state->plugin_desc->spd_id, "sending error %d with closest match = " "%s%s%s\n", cbdata.result, cbdata.closest_match ? "\"" : "", cbdata.closest_match ? cbdata.closest_match : "(null)", cbdata.closest_match ? "\"" : ""); slapi_pblock_set(cbdata.pb, SLAPI_PLUGIN_OPRETURN, &cbdata.result); /* XXX - THIS IS NOT A PUBLIC FUNCTION, but * slapi_send_ldap_result() stores the values we pass in, calls * the backend functions, which then overwrite the matched-dn * with a "real" entry's name before sending back the result. * If we return a -1 here, we prevent backends from being * called, but then no result gets sent if we use * slapi_send_ldap_result(), so we call the internal * send_ldap_result() function directly. */ send_ldap_result(cbdata.pb, cbdata.result, cbdata.closest_match, cbdata.text, cbdata.n_entries, NULL); } slapi_sdn_free(&cbdata.target_dn); free(cbdata.closest_match); free(cbdata.text); return cbdata.answer ? -1 : 0; } /* Locate the entry for a given DN. */ struct backend_locate_cbdata { struct plugin_state *state; char *target; Slapi_DN *target_dn; struct backend_entry_data *entry_data; }; /* Check if the target DN is an entry in this container's set of entries. If * it is, pull the entry's data out and save it. */ static bool_t backend_locate_cb(const char *group, const char *set, bool_t flag, void *backend_set_data, void *cb_data) { struct backend_locate_cbdata *cbdata; struct backend_set_data *set_data; struct backend_entry_data *entry_data; Slapi_RDN *rdn; const char *rdnstr, *ndn, *original_dn; unsigned int ndnlen; cbdata = cb_data; set_data = backend_set_data; /* Check if the target DN looks like it would be part of this set. */ if (slapi_sdn_scope_test(cbdata->target_dn, set_data->container_sdn, LDAP_SCOPE_ONELEVEL)) { /* Pull out the RDN and check for an entry which is using the * RDN as a key. */ rdn = slapi_rdn_new_sdn(cbdata->target_dn); if (rdn != NULL) { rdnstr = slapi_rdn_get_rdn(rdn); if (map_match(cbdata->state, group, set, &flag, strlen(rdnstr), rdnstr, &ndnlen, &ndn, &original_dn, (void **) &entry_data)) { if (entry_data != NULL) { cbdata->entry_data = entry_data; } } slapi_rdn_free(&rdn); } } return TRUE; } static void backend_locate(Slapi_PBlock *pb, struct backend_entry_data **data) { struct backend_locate_cbdata cbdata; slapi_pblock_get(pb, SLAPI_PLUGIN_PRIVATE, &cbdata.state); if (cbdata.state->plugin_base == NULL) { /* The plugin was not actually started. */ *data = NULL; return; } slapi_pblock_get(pb, SLAPI_TARGET_DN, &cbdata.target); cbdata.target_dn = slapi_sdn_new_dn_byval(cbdata.target); cbdata.entry_data = NULL; map_data_foreach_map(cbdata.state, NULL, backend_locate_cb, &cbdata); *data = cbdata.entry_data; slapi_sdn_free(&cbdata.target_dn); } /* Check if the target DN is part of this group's tree. If it is, return an * insufficient-access error. */ struct backend_group_check_scope_cbdata { struct plugin_state *state; const char *target; Slapi_DN *target_dn; bool_t ours; }; static bool_t backend_group_check_scope_cb(const char *group, void *cb_data) { struct backend_group_check_scope_cbdata *cbdata; Slapi_DN *group_dn; cbdata = cb_data; group_dn = slapi_sdn_new_dn_byref(group); if (slapi_sdn_scope_test(cbdata->target_dn, group_dn, LDAP_SCOPE_SUBTREE)) { cbdata->ours = TRUE; } slapi_sdn_free(&group_dn); return TRUE; } static bool_t backend_check_scope_pb(Slapi_PBlock *pb) { struct backend_group_check_scope_cbdata cbdata; slapi_pblock_get(pb, SLAPI_PLUGIN_PRIVATE, &cbdata.state); if (cbdata.state->plugin_base == NULL) { /* The plugin was not actually started. */ return FALSE; } slapi_pblock_get(pb, SLAPI_TARGET_DN, &cbdata.target); cbdata.target_dn = slapi_sdn_new_dn_byval(cbdata.target); cbdata.ours = FALSE; map_data_foreach_domain(cbdata.state, backend_group_check_scope_cb, &cbdata); slapi_sdn_free(&cbdata.target_dn); return cbdata.ours; } static int backend_write_cb(Slapi_PBlock *pb) { int ret; if (wrap_get_call_level() > 0) { return 0; } wrap_inc_call_level(); map_rdlock(); if (backend_check_scope_pb(pb)) { slapi_send_ldap_result(pb, LDAP_UNWILLING_TO_PERFORM, NULL, NULL, 0, NULL); ret = -1; } else { ret = 0; } map_unlock(); wrap_dec_call_level(); return ret; } static int backend_bind_cb(Slapi_PBlock *pb) { struct backend_entry_data *data; int ret; struct berval ref; struct berval *urls[] = {&ref, NULL}; const char *ndn; if (wrap_get_call_level() > 0) { return 0; } wrap_inc_call_level(); map_rdlock(); backend_locate(pb, &data); if (data != NULL) { ndn = slapi_sdn_get_ndn(data->original_entry_dn); ref.bv_len = strlen("ldap:///") + strlen(ndn); ref.bv_val = malloc(ref.bv_len + 1); if (ref.bv_val != NULL) { sprintf(ref.bv_val, "ldap:///%s", ndn); slapi_send_ldap_result(pb, LDAP_REFERRAL, NULL, NULL, 0, urls); free(ref.bv_val); } else { slapi_send_ldap_result(pb, LDAP_BUSY, NULL, NULL, 0, NULL); } ret = -1; } else { if (backend_check_scope_pb(pb)) { slapi_send_ldap_result(pb, LDAP_INVALID_CREDENTIALS, NULL, NULL, 0, NULL); ret = -1; } else { ret = 0; } } map_unlock(); wrap_dec_call_level(); return ret; } static int backend_compare_cb(Slapi_PBlock *pb) { int ret; if (wrap_get_call_level() > 0) { return 0; } wrap_inc_call_level(); map_rdlock(); if (backend_check_scope_pb(pb)) { slapi_send_ldap_result(pb, LDAP_UNWILLING_TO_PERFORM, NULL, NULL, 0, NULL); ret = -1; } else { ret = 0; } map_unlock(); wrap_dec_call_level(); return ret; } /* Warn if a set is empty. */ void backend_check_empty(struct plugin_state *state, const char *group, const char *set) { unsigned int first_key_len, first_value_len; int first_key_index; const char *first_id; char *first_key, *first_value; bool_t map_secure; if (!map_first(state, group, set, &map_secure, &first_key_len, &first_key, &first_value_len, &first_value, &first_id, &first_key_index)) { slapi_log_error(SLAPI_LOG_FATAL, state->plugin_desc->spd_id, "warning: no entries set up under %s%s%s\n", set, strlen(set) ? ", " : "", group); } } /* Populate our data cache. */ void backend_startup(Slapi_PBlock *pb, struct plugin_state *state) { backend_shr_startup(state, pb, SCH_CONTAINER_CONFIGURATION_FILTER); } int backend_init_preop(Slapi_PBlock *pb, struct plugin_state *state) { slapi_log_error(SLAPI_LOG_PLUGIN, state->plugin_desc->spd_id, "hooking up preoperation callbacks\n"); /* Intercept bind requests and return a referral or failure for entries * that we're managing. */ if (slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_BIND_FN, backend_bind_cb) != 0) { slapi_log_error(SLAPI_LOG_PLUGIN, state->plugin_desc->spd_id, "error hooking up bind callback\n"); return -1; } /* Intercept compare requests and return the right data. */ if (slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_COMPARE_FN, backend_compare_cb) != 0) { slapi_log_error(SLAPI_LOG_PLUGIN, state->plugin_desc->spd_id, "error hooking up compare callback\n"); return -1; } /* Intercept search requests and return the right data. */ if (slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_SEARCH_FN, backend_search_cb) != 0) { slapi_log_error(SLAPI_LOG_PLUGIN, state->plugin_desc->spd_id, "error hooking up search callback\n"); return -1; } /* Intercept write requests and return an insufficient-access error for * attempts to write to anything we're managing. */ if (slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_ADD_FN, backend_write_cb) != 0) { slapi_log_error(SLAPI_LOG_PLUGIN, state->plugin_desc->spd_id, "error hooking up add callback\n"); return -1; } if (slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_MODIFY_FN, backend_write_cb) != 0) { slapi_log_error(SLAPI_LOG_PLUGIN, state->plugin_desc->spd_id, "error hooking up modify callback\n"); return -1; } if (slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_MODRDN_FN, backend_write_cb) != 0) { slapi_log_error(SLAPI_LOG_PLUGIN, state->plugin_desc->spd_id, "error hooking up modrdn callback\n"); return -1; } if (slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_DELETE_FN, backend_write_cb) != 0) { slapi_log_error(SLAPI_LOG_PLUGIN, state->plugin_desc->spd_id, "error hooking up delete callback\n"); return -1; } /* We don't hook abandonment requests. */ /* We don't hook unbind requests. */ return 0; } int backend_init_postop(Slapi_PBlock *pb, struct plugin_state *state) { slapi_log_error(SLAPI_LOG_PLUGIN, state->plugin_desc->spd_id, "hooking up postoperation callbacks\n"); return backend_shr_postop_init(pb, state); } int backend_init_internal_postop(Slapi_PBlock *pb, struct plugin_state *state) { slapi_log_error(SLAPI_LOG_PLUGIN, state->plugin_desc->spd_id, "hooking up internal postoperation callbacks\n"); return backend_shr_internal_postop_init(pb, state); } #ifdef USE_SLAPI_BE_TXNS int backend_init_be_txn_postop(Slapi_PBlock *pb, struct plugin_state *state) { slapi_log_error(SLAPI_LOG_PLUGIN, state->plugin_desc->spd_id, "hooking up be-txn postoperation callbacks\n"); return backend_shr_be_txn_postop_init(pb, state); } #endif