summaryrefslogtreecommitdiff
path: root/src/providers/ldap/sdap_async_enum.c
diff options
context:
space:
mode:
authorJakub Hrozek <jhrozek@redhat.com>2013-08-27 11:59:10 +0200
committerJakub Hrozek <jhrozek@redhat.com>2013-08-28 18:06:57 +0200
commit8ca73915a3bf60331468fed6b3b38652c979f95d (patch)
tree171ddde5d4870d00f71b2953804a033c6f418180 /src/providers/ldap/sdap_async_enum.c
parent25e64abcac8db1d6a9efc7195259f760cebede54 (diff)
downloadsssd-8ca73915a3bf60331468fed6b3b38652c979f95d.tar.gz
sssd-8ca73915a3bf60331468fed6b3b38652c979f95d.tar.bz2
sssd-8ca73915a3bf60331468fed6b3b38652c979f95d.zip
LDAP: Move the ldap enum request to its own reusable module
The LDAP enumeration was too closely tied to the LDAP identity provider. Because some providers might need special handling such as refresh the master domain record before proceeding with the enumeration itself, this patch splits the request itself to a separate async request and lets the ldap_id_enum.c module only configure this new request. Also move the enum timestamp to sdap_domain to make the enum tracking per sdap domain. The cleanup timestamp will be moved in another patch.
Diffstat (limited to 'src/providers/ldap/sdap_async_enum.c')
-rw-r--r--src/providers/ldap/sdap_async_enum.c687
1 files changed, 687 insertions, 0 deletions
diff --git a/src/providers/ldap/sdap_async_enum.c b/src/providers/ldap/sdap_async_enum.c
new file mode 100644
index 00000000..ab6ae077
--- /dev/null
+++ b/src/providers/ldap/sdap_async_enum.c
@@ -0,0 +1,687 @@
+/*
+ SSSD
+
+ LDAP Enumeration Module
+
+ Authors:
+ Simo Sorce <ssorce@redhat.com>
+ Jakub Hrozek <jhrozek@redhat.com>
+
+ Copyright (C) 2013 Red Hat
+
+ 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; either version 3 of the License, or
+ (at your option) any later version.
+
+ 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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <errno.h>
+
+#include "util/util.h"
+#include "db/sysdb.h"
+#include "providers/ldap/ldap_common.h"
+#include "providers/ldap/sdap_async.h"
+#include "providers/ldap/sdap_idmap.h"
+
+extern struct tevent_req *ldap_id_cleanup_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *ctx);
+
+static struct tevent_req *enum_users_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *ctx,
+ struct sdap_domain *sdom,
+ struct sdap_id_op *op,
+ bool purge);
+static errno_t enum_users_recv(struct tevent_req *req);
+
+static struct tevent_req *enum_groups_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *ctx,
+ struct sdap_domain *sdom,
+ struct sdap_id_op *op,
+ bool purge);
+static errno_t enum_groups_recv(struct tevent_req *req);
+
+/* ==Enumeration-Request==================================================== */
+struct sdap_dom_enum_state {
+ struct tevent_context *ev;
+ struct sdap_id_ctx *ctx;
+ struct sdap_domain *sdom;
+ struct sdap_id_conn_ctx *conn;
+ struct sdap_id_op *op;
+
+ bool purge;
+};
+
+static errno_t sdap_dom_enum_retry(struct tevent_req *req);
+static void sdap_dom_enum_conn_done(struct tevent_req *subreq);
+static void sdap_dom_enum_users_done(struct tevent_req *subreq);
+static void sdap_dom_enum_groups_done(struct tevent_req *subreq);
+static void sdap_dom_enum_services_done(struct tevent_req *subreq);
+static void sdap_dom_enum_cleanup_done(struct tevent_req *subreq);
+
+struct tevent_req *
+sdap_dom_enum_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *ctx,
+ struct sdap_domain *sdom,
+ struct sdap_id_conn_ctx *conn)
+{
+ struct tevent_req *req;
+ struct sdap_dom_enum_state *state;
+ int t;
+ errno_t ret;
+
+ req = tevent_req_create(ctx, &state, struct sdap_dom_enum_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->ctx = ctx;
+ state->sdom = sdom;
+ state->conn = conn;
+ state->op = sdap_id_op_create(state, state->ctx->conn->conn_cache);
+ if (!state->op) {
+ DEBUG(SSSDBG_CRIT_FAILURE, ("sdap_id_op_create failed\n"));
+ ret = EIO;
+ goto fail;
+ }
+
+ sdom->last_enum = tevent_timeval_current();
+
+ t = dp_opt_get_int(ctx->opts->basic, SDAP_CACHE_PURGE_TIMEOUT);
+ if ((ctx->last_purge.tv_sec + t) < sdom->last_enum.tv_sec) {
+ state->purge = true;
+ }
+
+ ret = sdap_dom_enum_retry(req);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, ("ldap_id_enumerate_retry failed\n"));
+ goto fail;
+ }
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static errno_t sdap_dom_enum_retry(struct tevent_req *req)
+{
+ struct sdap_dom_enum_state *state = tevent_req_data(req,
+ struct sdap_dom_enum_state);
+ struct tevent_req *subreq;
+ errno_t ret;
+
+ subreq = sdap_id_op_connect_send(state->op, state, &ret);
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ ("sdap_id_op_connect_send failed: %d\n", ret));
+ return ret;
+ }
+
+ tevent_req_set_callback(subreq, sdap_dom_enum_conn_done, req);
+ return EOK;
+}
+
+static void sdap_dom_enum_conn_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_dom_enum_state *state = tevent_req_data(req,
+ struct sdap_dom_enum_state);
+ int ret, dp_error;
+
+ ret = sdap_id_op_connect_recv(subreq, &dp_error);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ if (dp_error == DP_ERR_OFFLINE) {
+ DEBUG(SSSDBG_TRACE_FUNC,
+ ("Backend is marked offline, retry later!\n"));
+ tevent_req_done(req);
+ } else {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ ("Domain enumeration failed to connect to " \
+ "LDAP server: (%d)[%s]\n", ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ }
+ return;
+ }
+
+ subreq = enum_users_send(state, state->ev,
+ state->ctx, state->sdom,
+ state->op, state->purge);
+ if (subreq == NULL) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_dom_enum_users_done, req);
+}
+
+static void sdap_dom_enum_users_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_dom_enum_state *state = tevent_req_data(req,
+ struct sdap_dom_enum_state);
+ uint64_t err = 0;
+ int ret, dp_error = DP_ERR_FATAL;
+
+ err = enum_users_recv(subreq);
+ talloc_zfree(subreq);
+ if (err != EOK && err != ENOENT) {
+ /* We call sdap_id_op_done only on error
+ * as the connection is reused by groups enumeration */
+ ret = sdap_id_op_done(state->op, (int)err, &dp_error);
+ if (dp_error == DP_ERR_OK) {
+ /* retry */
+ ret = sdap_dom_enum_retry(req);
+ if (ret == EOK) {
+ return;
+ }
+
+ dp_error = DP_ERR_FATAL;
+ }
+
+ if (dp_error == DP_ERR_OFFLINE) {
+ tevent_req_done(req);
+ } else {
+ DEBUG(SSSDBG_OP_FAILURE,
+ ("User enumeration failed with: (%d)[%s]\n",
+ ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ }
+ return;
+ }
+
+ subreq = enum_groups_send(state, state->ev, state->ctx,
+ state->sdom,
+ state->op, state->purge);
+ if (subreq == NULL) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_dom_enum_groups_done, req);
+}
+
+static void sdap_dom_enum_groups_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_dom_enum_state *state = tevent_req_data(req,
+ struct sdap_dom_enum_state);
+ uint64_t err = 0;
+ int ret, dp_error = DP_ERR_FATAL;
+
+ err = enum_groups_recv(subreq);
+ talloc_zfree(subreq);
+ if (err != EOK && err != ENOENT) {
+ /* We call sdap_id_op_done only on error
+ * as the connection is reused by services enumeration */
+ ret = sdap_id_op_done(state->op, (int)err, &dp_error);
+ if (dp_error == DP_ERR_OK && ret != EOK) {
+ /* retry */
+ ret = sdap_dom_enum_retry(req);
+ if (ret == EOK) {
+ return;
+ }
+
+ dp_error = DP_ERR_FATAL;
+ }
+
+ if (ret != EOK) {
+ if (dp_error == DP_ERR_OFFLINE) {
+ tevent_req_done(req);
+ } else {
+ DEBUG(SSSDBG_OP_FAILURE,
+ ("Group enumeration failed with: (%d)[%s]\n",
+ ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ }
+
+ return;
+ }
+ }
+
+ subreq = enum_services_send(state, state->ev, state->ctx,
+ state->op, state->purge);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(subreq, sdap_dom_enum_services_done, req);
+}
+
+static void sdap_dom_enum_services_done(struct tevent_req *subreq)
+{
+ errno_t ret;
+ int dp_error = DP_ERR_FATAL;
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct sdap_dom_enum_state *state = tevent_req_data(req,
+ struct sdap_dom_enum_state);
+
+ ret = enum_services_recv(subreq);
+ talloc_zfree(subreq);
+ if (ret == ENOENT) ret = EOK;
+
+ /* All enumerations are complete, so conclude the
+ * id_op
+ */
+ ret = sdap_id_op_done(state->op, ret, &dp_error);
+ if (dp_error == DP_ERR_OK && ret != EOK) {
+ /* retry */
+ ret = sdap_dom_enum_retry(req);
+ if (ret == EOK) {
+ return;
+ }
+
+ dp_error = DP_ERR_FATAL;
+ }
+
+ if (ret != EOK) {
+ if (dp_error == DP_ERR_OFFLINE) {
+ tevent_req_done(req);
+ } else {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ ("Service enumeration failed with: (%d)[%s]\n",
+ ret, strerror(ret)));
+ tevent_req_error(req, ret);
+ }
+
+ return;
+ }
+
+ /* Ok, we've completed an enumeration. Save this to the
+ * sysdb so we can postpone starting up the enumeration
+ * process on the next SSSD service restart (to avoid
+ * slowing down system boot-up
+ */
+ ret = sysdb_set_enumerated(state->sdom->dom->sysdb,
+ state->sdom->dom, true);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ ("Could not mark domain as having enumerated.\n"));
+ /* This error is non-fatal, so continue */
+ }
+
+ if (state->purge) {
+ subreq = ldap_id_cleanup_send(state, state->ev, state->ctx);
+ if (!subreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ tevent_req_set_callback(subreq, sdap_dom_enum_cleanup_done, req);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static void sdap_dom_enum_cleanup_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ talloc_zfree(subreq);
+ tevent_req_done(req);
+}
+
+errno_t sdap_dom_enum_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+/* ==User-Enumeration===================================================== */
+struct enum_users_state {
+ struct tevent_context *ev;
+ struct sdap_id_ctx *ctx;
+ struct sdap_domain *sdom;
+ struct sdap_id_op *op;
+
+ char *filter;
+ const char **attrs;
+};
+
+static void enum_users_done(struct tevent_req *subreq);
+
+static struct tevent_req *enum_users_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *ctx,
+ struct sdap_domain *sdom,
+ struct sdap_id_op *op,
+ bool purge)
+{
+ struct tevent_req *req, *subreq;
+ struct enum_users_state *state;
+ int ret;
+ bool use_mapping;
+
+ req = tevent_req_create(memctx, &state, struct enum_users_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->sdom = sdom;
+ state->ctx = ctx;
+ state->op = op;
+
+ use_mapping = sdap_idmap_domain_has_algorithmic_mapping(
+ ctx->opts->idmap_ctx,
+ sdom->dom->domain_id);
+
+ /* We always want to filter on objectclass and an available name */
+ state->filter = talloc_asprintf(state,
+ "(&(objectclass=%s)(%s=*)",
+ ctx->opts->user_map[SDAP_OC_USER].name,
+ ctx->opts->user_map[SDAP_AT_USER_NAME].name);
+ if (!state->filter) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ ("Failed to build base filter\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ if (use_mapping) {
+ /* If we're ID-mapping, check for the objectSID as well */
+ state->filter = talloc_asprintf_append_buffer(
+ state->filter, "(%s=*)",
+ ctx->opts->user_map[SDAP_AT_USER_OBJECTSID].name);
+ } else {
+ /* We're not ID-mapping, so make sure to only get entries
+ * that have UID and GID
+ */
+ state->filter = talloc_asprintf_append_buffer(
+ state->filter, "(%s=*)(%s=*)",
+ ctx->opts->user_map[SDAP_AT_USER_UID].name,
+ ctx->opts->user_map[SDAP_AT_USER_GID].name);
+ }
+ if (!state->filter) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ ("Failed to build base filter\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ if (ctx->srv_opts && ctx->srv_opts->max_user_value && !purge) {
+ /* If we have lastUSN available and we're not doing a full
+ * refresh, limit to changes with a higher entryUSN value.
+ */
+ state->filter = talloc_asprintf_append_buffer(
+ state->filter,
+ "(%s>=%s)(!(%s=%s))",
+ ctx->opts->user_map[SDAP_AT_USER_USN].name,
+ ctx->srv_opts->max_user_value,
+ ctx->opts->user_map[SDAP_AT_USER_USN].name,
+ ctx->srv_opts->max_user_value);
+
+ if (!state->filter) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ ("Failed to build base filter\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+ }
+
+ /* Terminate the search filter */
+ state->filter = talloc_asprintf_append_buffer(state->filter, ")");
+ if (!state->filter) {
+ DEBUG(2, ("Failed to build base filter\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ /* TODO: handle attrs_type */
+ ret = build_attrs_from_map(state, ctx->opts->user_map, SDAP_OPTS_USER,
+ NULL, &state->attrs, NULL);
+ if (ret != EOK) goto fail;
+
+ /* TODO: restrict the enumerations to using a single
+ * search base at a time.
+ */
+
+ subreq = sdap_get_users_send(state, state->ev,
+ state->sdom->dom,
+ state->sdom->dom->sysdb,
+ state->ctx->opts,
+ state->sdom->user_search_bases,
+ sdap_id_op_handle(state->op),
+ state->attrs, state->filter,
+ dp_opt_get_int(state->ctx->opts->basic,
+ SDAP_ENUM_SEARCH_TIMEOUT),
+ true);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, enum_users_done, req);
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void enum_users_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct enum_users_state *state = tevent_req_data(req,
+ struct enum_users_state);
+ char *usn_value;
+ char *endptr = NULL;
+ unsigned usn_number;
+ int ret;
+
+ ret = sdap_get_users_recv(subreq, state, &usn_value);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (usn_value) {
+ talloc_zfree(state->ctx->srv_opts->max_user_value);
+ state->ctx->srv_opts->max_user_value =
+ talloc_steal(state->ctx, usn_value);
+
+ usn_number = strtoul(usn_value, &endptr, 10);
+ if ((endptr == NULL || (*endptr == '\0' && endptr != usn_value))
+ && (usn_number > state->ctx->srv_opts->last_usn)) {
+ state->ctx->srv_opts->last_usn = usn_number;
+ }
+ }
+
+ DEBUG(4, ("Users higher USN value: [%s]\n",
+ state->ctx->srv_opts->max_user_value));
+
+ tevent_req_done(req);
+}
+
+static errno_t enum_users_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+/* =Group-Enumeration===================================================== */
+struct enum_groups_state {
+ struct tevent_context *ev;
+ struct sdap_id_ctx *ctx;
+ struct sdap_domain *sdom;
+ struct sdap_id_op *op;
+
+ char *filter;
+ const char **attrs;
+};
+
+static void enum_groups_done(struct tevent_req *subreq);
+
+static struct tevent_req *enum_groups_send(TALLOC_CTX *memctx,
+ struct tevent_context *ev,
+ struct sdap_id_ctx *ctx,
+ struct sdap_domain *sdom,
+ struct sdap_id_op *op,
+ bool purge)
+{
+ struct tevent_req *req, *subreq;
+ struct enum_groups_state *state;
+ int ret;
+ bool use_mapping;
+
+ req = tevent_req_create(memctx, &state, struct enum_groups_state);
+ if (!req) return NULL;
+
+ state->ev = ev;
+ state->sdom = sdom;
+ state->ctx = ctx;
+ state->op = op;
+
+ use_mapping = sdap_idmap_domain_has_algorithmic_mapping(
+ ctx->opts->idmap_ctx,
+ sdom->dom->domain_id);
+
+ /* We always want to filter on objectclass and an available name */
+ state->filter = talloc_asprintf(state,
+ "(&(objectclass=%s)(%s=*)",
+ ctx->opts->group_map[SDAP_OC_GROUP].name,
+ ctx->opts->group_map[SDAP_AT_GROUP_NAME].name);
+ if (!state->filter) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ ("Failed to build base filter\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ if (use_mapping) {
+ /* If we're ID-mapping, check for the objectSID as well */
+ state->filter = talloc_asprintf_append_buffer(
+ state->filter, "(%s=*)",
+ ctx->opts->group_map[SDAP_AT_GROUP_OBJECTSID].name);
+ } else {
+ /* We're not ID-mapping, so make sure to only get entries
+ * that have a non-zero GID.
+ */
+ state->filter = talloc_asprintf_append_buffer(
+ state->filter, "(&(%s=*)(!(%s=0)))",
+ ctx->opts->group_map[SDAP_AT_GROUP_GID].name,
+ ctx->opts->group_map[SDAP_AT_GROUP_GID].name);
+ }
+ if (!state->filter) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ ("Failed to build base filter\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ if (ctx->srv_opts && ctx->srv_opts->max_group_value && !purge) {
+ state->filter = talloc_asprintf_append_buffer(
+ state->filter,
+ "(%s>=%s)(!(%s=%s))",
+ ctx->opts->group_map[SDAP_AT_GROUP_USN].name,
+ ctx->srv_opts->max_group_value,
+ ctx->opts->group_map[SDAP_AT_GROUP_USN].name,
+ ctx->srv_opts->max_group_value);
+ if (!state->filter) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ ("Failed to build base filter\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+ }
+
+ /* Terminate the search filter */
+ state->filter = talloc_asprintf_append_buffer(state->filter, ")");
+ if (!state->filter) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ ("Failed to build base filter\n"));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ /* TODO: handle attrs_type */
+ ret = build_attrs_from_map(state, ctx->opts->group_map, SDAP_OPTS_GROUP,
+ NULL, &state->attrs, NULL);
+ if (ret != EOK) goto fail;
+
+ /* TODO: restrict the enumerations to using a single
+ * search base at a time.
+ */
+
+ subreq = sdap_get_groups_send(state, state->ev,
+ state->sdom,
+ state->ctx->opts,
+ sdap_id_op_handle(state->op),
+ state->attrs, state->filter,
+ dp_opt_get_int(state->ctx->opts->basic,
+ SDAP_ENUM_SEARCH_TIMEOUT),
+ true);
+ if (!subreq) {
+ ret = ENOMEM;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, enum_groups_done, req);
+
+ return req;
+
+fail:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static void enum_groups_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct enum_groups_state *state = tevent_req_data(req,
+ struct enum_groups_state);
+ char *usn_value;
+ char *endptr = NULL;
+ unsigned usn_number;
+ int ret;
+
+ ret = sdap_get_groups_recv(subreq, state, &usn_value);
+ talloc_zfree(subreq);
+ if (ret) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ if (usn_value) {
+ talloc_zfree(state->ctx->srv_opts->max_group_value);
+ state->ctx->srv_opts->max_group_value =
+ talloc_steal(state->ctx, usn_value);
+ usn_number = strtoul(usn_value, &endptr, 10);
+ if ((endptr == NULL || (*endptr == '\0' && endptr != usn_value))
+ && (usn_number > state->ctx->srv_opts->last_usn)) {
+ state->ctx->srv_opts->last_usn = usn_number;
+ }
+ }
+
+ DEBUG(4, ("Groups higher USN value: [%s]\n",
+ state->ctx->srv_opts->max_group_value));
+
+ tevent_req_done(req);
+}
+
+static errno_t enum_groups_recv(struct tevent_req *req)
+{
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}