sdap_async_sudo.c revision e9ae5cd285dcc8fa232e16f9c7a29f18537272f2
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu/*
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu SSSD
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu Async LDAP Helper routines for sudo
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu Authors:
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu Pavel Březina <pbrezina@redhat.com>
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu Copyright (C) 2012 Red Hat
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu This program is free software; you can redistribute it and/or modify
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu it under the terms of the GNU General Public License as published by
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu the Free Software Foundation; either version 3 of the License, or
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu (at your option) any later version.
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu This program is distributed in the hope that it will be useful,
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu but WITHOUT ANY WARRANTY; without even the implied warranty of
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu GNU General Public License for more details.
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu You should have received a copy of the GNU General Public License
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu along with this program. If not, see <http://www.gnu.org/licenses/>.
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu*/
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu
3cbe795704877174d29dd463b98fd60daf0853bdGeorgel Calin#include <errno.h>
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu#include <talloc.h>
3cbe795704877174d29dd463b98fd60daf0853bdGeorgel Calin#include <tevent.h>
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu
3cbe795704877174d29dd463b98fd60daf0853bdGeorgel Calin#include "providers/dp_backend.h"
3cbe795704877174d29dd463b98fd60daf0853bdGeorgel Calin#include "providers/ldap/ldap_common.h"
3cbe795704877174d29dd463b98fd60daf0853bdGeorgel Calin#include "providers/ldap/sdap.h"
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu#include "providers/ldap/sdap_ops.h"
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu#include "providers/ldap/sdap_sudo.h"
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu#include "providers/ldap/sdap_sudo_cache.h"
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu#include "db/sysdb_sudo.h"
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanustruct sdap_sudo_load_sudoers_state {
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu struct sysdb_attrs **rules;
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu size_t num_rules;
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu};
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanustatic void sdap_sudo_load_sudoers_done(struct tevent_req *subreq);
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanustatic struct tevent_req *
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanusdap_sudo_load_sudoers_send(TALLOC_CTX *mem_ctx,
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu struct tevent_context *ev,
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu struct sdap_options *opts,
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu struct sdap_handle *sh,
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu const char *ldap_filter)
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu{
74b23191c8f49798f7513f324bbdbbe32f8a1db2Razvan Pascanu struct tevent_req *req;
struct tevent_req *subreq;
struct sdap_sudo_load_sudoers_state *state;
struct sdap_search_base **sb;
int ret;
req = tevent_req_create(mem_ctx, &state,
struct sdap_sudo_load_sudoers_state);
if (!req) {
return NULL;
}
state->rules = NULL;
state->num_rules = 0;
sb = opts->sdom->sudo_search_bases;
if (sb == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE,
"SUDOERS lookup request without a search base\n");
ret = EINVAL;
goto immediately;
}
DEBUG(SSSDBG_TRACE_FUNC, "About to fetch sudo rules\n");
subreq = sdap_search_bases_send(state, ev, opts, sh, sb,
opts->sudorule_map, true, 0,
ldap_filter, NULL);
if (subreq == NULL) {
ret = ENOMEM;
goto immediately;
}
tevent_req_set_callback(subreq, sdap_sudo_load_sudoers_done, req);
ret = EOK;
immediately:
if (ret != EOK) {
tevent_req_error(req, ret);
tevent_req_post(req, ev);
}
return req;
}
static void sdap_sudo_load_sudoers_done(struct tevent_req *subreq)
{
struct tevent_req *req;
struct sdap_sudo_load_sudoers_state *state;
errno_t ret;
req = tevent_req_callback_data(subreq, struct tevent_req);
state = tevent_req_data(req, struct sdap_sudo_load_sudoers_state);
ret = sdap_search_bases_recv(subreq, state, &state->num_rules,
&state->rules);
talloc_zfree(subreq);
if (ret != EOK) {
tevent_req_error(req, ret);
return;
}
DEBUG(SSSDBG_IMPORTANT_INFO, "Received %zu sudo rules\n",
state->num_rules);
tevent_req_done(req);
return;
}
static int sdap_sudo_load_sudoers_recv(struct tevent_req *req,
TALLOC_CTX *mem_ctx,
size_t *num_rules,
struct sysdb_attrs ***rules)
{
struct sdap_sudo_load_sudoers_state *state;
state = tevent_req_data(req, struct sdap_sudo_load_sudoers_state);
TEVENT_REQ_RETURN_ON_ERROR(req);
*num_rules = state->num_rules;
*rules = talloc_steal(mem_ctx, state->rules);
return EOK;
}
static int sdap_sudo_purge_sudoers(struct sss_domain_info *dom,
const char *filter,
struct sdap_attr_map *map,
size_t rules_count,
struct sysdb_attrs **rules)
{
const char *name;
size_t i;
errno_t ret;
if (filter == NULL) {
/* removes downloaded rules from the cache */
if (rules_count == 0 || rules == NULL) {
return EOK;
}
for (i = 0; i < rules_count; i++) {
ret = sysdb_attrs_get_string(rules[i],
map[SDAP_AT_SUDO_NAME].sys_name,
&name);
if (ret != EOK) {
DEBUG(SSSDBG_MINOR_FAILURE,
"Failed to retrieve rule name: [%s]\n", strerror(ret));
continue;
}
ret = sysdb_sudo_purge_byname(dom, name);
if (ret != EOK) {
DEBUG(SSSDBG_MINOR_FAILURE,
"Failed to delete rule %s: [%s]\n",
name, strerror(ret));
continue;
}
}
ret = EOK;
} else {
/* purge cache by provided filter */
ret = sysdb_sudo_purge_byfilter(dom, filter);
if (ret != EOK) {
goto done;
}
}
done:
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, "failed to purge sudo rules [%d]: %s\n",
ret, strerror(ret));
}
return ret;
}
static int sdap_sudo_store_sudoers(TALLOC_CTX *mem_ctx,
struct sss_domain_info *domain,
struct sdap_options *opts,
size_t rules_count,
struct sysdb_attrs **rules,
int cache_timeout,
time_t now,
char **_usn)
{
errno_t ret;
/* Empty sudoers? Done. */
if (rules_count == 0 || rules == NULL) {
*_usn = NULL;
return EOK;
}
ret = sdap_save_native_sudorule_list(mem_ctx, domain,
opts->sudorule_map, rules,
rules_count, cache_timeout, now,
_usn);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, "failed to save sudo rules [%d]: %s\n",
ret, strerror(ret));
return ret;
}
return EOK;
}
static void sdap_sudo_set_usn(struct sdap_server_opts *srv_opts, char *usn)
{
unsigned int usn_number;
char *endptr = NULL;
if (srv_opts == NULL) {
DEBUG(SSSDBG_TRACE_FUNC, "Bug: srv_opts is NULL\n");
return;
}
if (usn == NULL) {
/* If the USN value is unknown and we don't have max_sudo_value set
* (possibly first full refresh which did not find any rule) we will
* set zero so smart refresh can pick up. */
if (srv_opts->max_sudo_value == NULL) {
srv_opts->max_sudo_value = talloc_strdup(srv_opts, "0");
if (srv_opts->max_sudo_value == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup() failed\n");
}
return;
}
DEBUG(SSSDBG_TRACE_FUNC, "Empty USN, ignoring\n");
return;
}
talloc_zfree(srv_opts->max_sudo_value);
srv_opts->max_sudo_value = talloc_steal(srv_opts, usn);
usn_number = strtoul(usn, &endptr, 10);
if ((endptr == NULL || (*endptr == '\0' && endptr != usn))
&& (usn_number > srv_opts->last_usn)) {
srv_opts->last_usn = usn_number;
}
DEBUG(SSSDBG_FUNC_DATA, "SUDO higher USN value: [%s]\n",
srv_opts->max_sudo_value);
}
static char *sdap_sudo_build_host_filter(TALLOC_CTX *mem_ctx,
struct sdap_attr_map *map,
char **hostnames,
char **ip_addr,
bool netgroups,
bool regexp)
{
TALLOC_CTX *tmp_ctx = NULL;
char *filter = NULL;
int i;
tmp_ctx = talloc_new(NULL);
if (tmp_ctx == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n");
return NULL;
}
filter = talloc_strdup(tmp_ctx, "(|");
if (filter == NULL) {
goto done;
}
/* sudoHost is not specified */
filter = talloc_asprintf_append_buffer(filter, "(!(%s=*))",
map[SDAP_AT_SUDO_HOST].name);
if (filter == NULL) {
goto done;
}
/* ALL */
filter = talloc_asprintf_append_buffer(filter, "(%s=ALL)",
map[SDAP_AT_SUDO_HOST].name);
if (filter == NULL) {
goto done;
}
/* hostnames */
if (hostnames != NULL) {
for (i = 0; hostnames[i] != NULL; i++) {
filter = talloc_asprintf_append_buffer(filter, "(%s=%s)",
map[SDAP_AT_SUDO_HOST].name,
hostnames[i]);
if (filter == NULL) {
goto done;
}
}
}
/* ip addresses and networks */
if (ip_addr != NULL) {
for (i = 0; ip_addr[i] != NULL; i++) {
filter = talloc_asprintf_append_buffer(filter, "(%s=%s)",
map[SDAP_AT_SUDO_HOST].name,
ip_addr[i]);
if (filter == NULL) {
goto done;
}
}
}
/* sudoHost contains netgroup - will be filtered more by sudo */
if (netgroups) {
filter = talloc_asprintf_append_buffer(filter, SDAP_SUDO_FILTER_NETGROUP,
map[SDAP_AT_SUDO_HOST].name,
"*");
if (filter == NULL) {
goto done;
}
}
/* sudoHost contains regexp - will be filtered more by sudo */
/* from sudo match.c :
* #define has_meta(s) (strpbrk(s, "\\?*[]") != NULL)
*/
if (regexp) {
filter = talloc_asprintf_append_buffer(filter,
"(|(%s=*\\\\*)(%s=*?*)(%s=*\\2A*)"
"(%s=*[*]*))",
map[SDAP_AT_SUDO_HOST].name,
map[SDAP_AT_SUDO_HOST].name,
map[SDAP_AT_SUDO_HOST].name,
map[SDAP_AT_SUDO_HOST].name);
if (filter == NULL) {
goto done;
}
}
filter = talloc_strdup_append_buffer(filter, ")");
if (filter == NULL) {
goto done;
}
talloc_steal(mem_ctx, filter);
done:
talloc_free(tmp_ctx);
return filter;
}
static char *sdap_sudo_get_filter(TALLOC_CTX *mem_ctx,
struct sdap_attr_map *map,
struct sdap_sudo_ctx *sudo_ctx,
const char *rule_filter)
{
TALLOC_CTX *tmp_ctx = NULL;
char *host_filter = NULL;
char *filter = NULL;
if (!sudo_ctx->use_host_filter) {
return talloc_strdup(mem_ctx, rule_filter);
}
tmp_ctx = talloc_new(NULL);
if (tmp_ctx == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n");
return NULL;
}
host_filter = sdap_sudo_build_host_filter(tmp_ctx, map,
sudo_ctx->hostnames,
sudo_ctx->ip_addr,
sudo_ctx->include_netgroups,
sudo_ctx->include_regexp);
if (host_filter == NULL) {
goto done;
}
filter = sdap_combine_filters(tmp_ctx, rule_filter, host_filter);
if (filter == NULL) {
goto done;
}
talloc_steal(mem_ctx, filter);
done:
talloc_free(tmp_ctx);
return filter;
}
struct sdap_sudo_refresh_state {
struct sdap_sudo_ctx *sudo_ctx;
struct tevent_context *ev;
struct sdap_server_opts *srv_opts;
struct sdap_options *opts;
struct sdap_id_op *sdap_op;
struct sysdb_ctx *sysdb;
struct sss_domain_info *domain;
const char *search_filter;
const char *delete_filter;
int dp_error;
size_t num_rules;
};
static errno_t sdap_sudo_refresh_retry(struct tevent_req *req);
static void sdap_sudo_refresh_connect_done(struct tevent_req *subreq);
static void sdap_sudo_refresh_hostinfo_done(struct tevent_req *subreq);
static errno_t sdap_sudo_refresh_sudoers(struct tevent_req *req);
static void sdap_sudo_refresh_done(struct tevent_req *subreq);
struct tevent_req *sdap_sudo_refresh_send(TALLOC_CTX *mem_ctx,
struct sdap_sudo_ctx *sudo_ctx,
const char *search_filter,
const char *delete_filter)
{
struct tevent_req *req;
struct sdap_sudo_refresh_state *state;
struct sdap_id_ctx *id_ctx = sudo_ctx->id_ctx;
int ret;
req = tevent_req_create(mem_ctx, &state, struct sdap_sudo_refresh_state);
if (!req) {
return NULL;
}
/* if we don't have a search filter, this request is meaningless */
if (search_filter == NULL) {
ret = EINVAL;
goto immediately;
}
state->sudo_ctx = sudo_ctx;
state->ev = id_ctx->be->ev;
state->opts = id_ctx->opts;
state->domain = id_ctx->be->domain;
state->sysdb = id_ctx->be->domain->sysdb;
state->dp_error = DP_ERR_FATAL;
state->sdap_op = sdap_id_op_create(state, id_ctx->conn->conn_cache);
if (!state->sdap_op) {
DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create() failed\n");
ret = ENOMEM;
goto immediately;
}
state->search_filter = talloc_strdup(state, search_filter);
if (state->search_filter == NULL) {
ret = ENOMEM;
goto immediately;
}
state->delete_filter = talloc_strdup(state, delete_filter);
if (delete_filter != NULL && state->delete_filter == NULL) {
ret = ENOMEM;
goto immediately;
}
ret = sdap_sudo_refresh_retry(req);
if (ret == EAGAIN) {
/* asynchronous processing */
return req;
}
immediately:
if (ret == EOK) {
tevent_req_done(req);
} else {
tevent_req_error(req, ret);
}
tevent_req_post(req, id_ctx->be->ev);
return req;
}
static errno_t sdap_sudo_refresh_retry(struct tevent_req *req)
{
struct sdap_sudo_refresh_state *state;
struct tevent_req *subreq;
int ret;
state = tevent_req_data(req, struct sdap_sudo_refresh_state);
subreq = sdap_id_op_connect_send(state->sdap_op, state, &ret);
if (subreq == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "sdap_id_op_connect_send() failed: "
"%d(%s)\n", ret, strerror(ret));
return ret;
}
tevent_req_set_callback(subreq, sdap_sudo_refresh_connect_done, req);
return EAGAIN;
}
static void sdap_sudo_refresh_connect_done(struct tevent_req *subreq)
{
struct tevent_req *req;
struct sdap_sudo_refresh_state *state;
int dp_error;
int ret;
req = tevent_req_callback_data(subreq, struct tevent_req);
state = tevent_req_data(req, struct sdap_sudo_refresh_state);
ret = sdap_id_op_connect_recv(subreq, &dp_error);
talloc_zfree(subreq);
if (ret != EOK) {
DEBUG(SSSDBG_CRIT_FAILURE, "SUDO LDAP connection failed "
"[%d]: %s\n", ret, strerror(ret));
state->dp_error = dp_error;
tevent_req_error(req, ret);
return;
}
DEBUG(SSSDBG_TRACE_FUNC, "SUDO LDAP connection successful\n");
/* Obtain srv_opts here in case of first connection. */
state->srv_opts = state->sudo_ctx->id_ctx->srv_opts;
/* Renew host information if needed. */
if (state->sudo_ctx->run_hostinfo) {
subreq = sdap_sudo_get_hostinfo_send(state, state->opts,
state->sudo_ctx->id_ctx->be);
if (subreq == NULL) {
state->dp_error = DP_ERR_FATAL;
tevent_req_error(req, ENOMEM);
return;
}
tevent_req_set_callback(subreq, sdap_sudo_refresh_hostinfo_done, req);
state->sudo_ctx->run_hostinfo = false;
return;
}
ret = sdap_sudo_refresh_sudoers(req);
if (ret != EAGAIN) {
state->dp_error = DP_ERR_FATAL;
tevent_req_error(req, ret);
}
}
static void sdap_sudo_refresh_hostinfo_done(struct tevent_req *subreq)
{
struct sdap_sudo_ctx *sudo_ctx;
struct sdap_sudo_refresh_state *state;
struct tevent_req *req;
errno_t ret;
req = tevent_req_callback_data(subreq, struct tevent_req);
state = tevent_req_data(req, struct sdap_sudo_refresh_state);
sudo_ctx = state->sudo_ctx;
ret = sdap_sudo_get_hostinfo_recv(sudo_ctx, subreq, &sudo_ctx->hostnames,
&sudo_ctx->ip_addr);
talloc_zfree(subreq);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, "Unable to retrieve host information, "
"host filter will be disabled [%d]: %s\n",
ret, sss_strerror(ret));
sudo_ctx->use_host_filter = false;
} else {
sudo_ctx->use_host_filter = true;
}
ret = sdap_sudo_refresh_sudoers(req);
if (ret != EAGAIN) {
state->dp_error = DP_ERR_FATAL;
tevent_req_error(req, ret);
}
}
static errno_t sdap_sudo_refresh_sudoers(struct tevent_req *req)
{
struct sdap_sudo_refresh_state *state;
struct tevent_req *subreq;
char *filter;
state = tevent_req_data(req, struct sdap_sudo_refresh_state);
/* We are connected. Host information may have changed during transition
* from offline to online state. At this point we can combine search
* and host filter. */
filter = sdap_sudo_get_filter(state, state->opts->sudorule_map,
state->sudo_ctx, state->search_filter);
if (filter == NULL) {
return ENOMEM;
}
subreq = sdap_sudo_load_sudoers_send(state, state->ev,
state->opts,
sdap_id_op_handle(state->sdap_op),
filter);
if (subreq == NULL) {
talloc_free(filter);
return ENOMEM;
}
tevent_req_set_callback(subreq, sdap_sudo_refresh_done, req);
return EAGAIN;
}
static void sdap_sudo_refresh_done(struct tevent_req *subreq)
{
struct tevent_req *req;
struct sdap_sudo_refresh_state *state;
struct sysdb_attrs **rules = NULL;
size_t rules_count = 0;
char *usn = NULL;
int dp_error;
int ret;
errno_t sret;
bool in_transaction = false;
time_t now;
req = tevent_req_callback_data(subreq, struct tevent_req);
state = tevent_req_data(req, struct sdap_sudo_refresh_state);
ret = sdap_sudo_load_sudoers_recv(subreq, state, &rules_count, &rules);
talloc_zfree(subreq);
ret = sdap_id_op_done(state->sdap_op, ret, &dp_error);
if (dp_error == DP_ERR_OK && ret != EOK) {
/* retry */
ret = sdap_sudo_refresh_retry(req);
if (ret != EOK) {
tevent_req_error(req, ret);
}
return;
}
DEBUG(SSSDBG_TRACE_FUNC, "Received %zu rules\n", rules_count);
/* start transaction */
ret = sysdb_transaction_start(state->sysdb);
if (ret != EOK) {
DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n");
goto done;
}
in_transaction = true;
/* purge cache */
ret = sdap_sudo_purge_sudoers(state->domain, state->delete_filter,
state->opts->sudorule_map, rules_count, rules);
if (ret != EOK) {
goto done;
}
/* store rules */
now = time(NULL);
ret = sdap_sudo_store_sudoers(state, state->domain,
state->opts, rules_count, rules,
state->domain->sudo_timeout, now, &usn);
if (ret != EOK) {
goto done;
}
/* commit transaction */
ret = sysdb_transaction_commit(state->sysdb);
if (ret != EOK) {
DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n");
goto done;
}
in_transaction = false;
DEBUG(SSSDBG_TRACE_FUNC, "Sudoers is successfuly stored in cache\n");
/* remember new usn */
sdap_sudo_set_usn(state->srv_opts, usn);
ret = EOK;
state->num_rules = rules_count;
done:
if (in_transaction) {
sret = sysdb_transaction_cancel(state->sysdb);
if (sret != EOK) {
DEBUG(SSSDBG_OP_FAILURE, "Could not cancel transaction\n");
}
}
state->dp_error = dp_error;
if (ret == EOK) {
tevent_req_done(req);
} else {
tevent_req_error(req, ret);
}
}
int sdap_sudo_refresh_recv(TALLOC_CTX *mem_ctx,
struct tevent_req *req,
int *dp_error,
size_t *num_rules)
{
struct sdap_sudo_refresh_state *state;
state = tevent_req_data(req, struct sdap_sudo_refresh_state);
TEVENT_REQ_RETURN_ON_ERROR(req);
*dp_error = state->dp_error;
if (num_rules != NULL) {
*num_rules = state->num_rules;
}
return EOK;
}