diff options
Diffstat (limited to '')
164 files changed, 69982 insertions, 0 deletions
diff --git a/src/responder/autofs/autofs_private.h b/src/responder/autofs/autofs_private.h new file mode 100644 index 0000000..c15e028 --- /dev/null +++ b/src/responder/autofs/autofs_private.h @@ -0,0 +1,74 @@ +/* + Authors: + Jakub Hrozek <jhrozek@redhat.com> + + Copyright (C) 2012 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/>. +*/ + +#ifndef _AUTOFSSRV_PRIVATE_H_ +#define _AUTOFSSRV_PRIVATE_H_ + +#include <dhash.h> + +#include "responder/common/responder.h" +#include "responder/common/cache_req/cache_req.h" + +#define SSS_AUTOFS_PROTO_VERSION 0x001 + +struct autofs_ctx { + struct resp_ctx *rctx; + + int neg_timeout; + + hash_table_t *maps; +}; + +struct autofs_cmd_ctx { + struct autofs_ctx *autofs_ctx; + struct cli_ctx *cli_ctx; + + const char *mapname; + const char *keyname; + uint32_t max_entries; + uint32_t cursor; +}; + +struct autofs_enum_ctx { + /* Results. First result is the map objects, next results are map entries. */ + struct cache_req_result *result; + + /* True if the map was found. */ + bool found; + + /* False if the result is being created. */ + bool ready; + + /* Enumeration context key. */ + const char *key; + + /* Hash table that contains this enumeration context. */ + hash_table_t *table; + + /* Requests that awaits the data. */ + struct setent_req_list *notify_list; +}; + +struct sss_cmd_table *get_autofs_cmds(void); +int autofs_connection_setup(struct cli_ctx *cctx); + +void autofs_orphan_maps(struct autofs_ctx *actx); + +#endif /* _AUTOFSSRV_PRIVATE_H_ */ diff --git a/src/responder/autofs/autofssrv.c b/src/responder/autofs/autofssrv.c new file mode 100644 index 0000000..1dbbe9f --- /dev/null +++ b/src/responder/autofs/autofssrv.c @@ -0,0 +1,242 @@ +/* + Authors: + Jakub Hrozek <jhrozek@redhat.com> + + Copyright (C) 2012 Red Hat + + Autofs responder: the responder server + + 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 <popt.h> + +#include "util/util.h" +#include "confdb/confdb.h" +#include "responder/common/responder.h" +#include "providers/data_provider.h" +#include "responder/autofs/autofs_private.h" +#include "sss_iface/sss_iface_async.h" +#include "util/sss_ptr_hash.h" + +static errno_t +autofs_get_config(struct autofs_ctx *actx, + struct confdb_ctx *cdb) +{ + errno_t ret; + + ret = confdb_get_int(cdb, CONFDB_AUTOFS_CONF_ENTRY, + CONFDB_AUTOFS_MAP_NEG_TIMEOUT, 15, + &actx->neg_timeout); + return ret; +} + +static errno_t +autofs_clean_hash_table(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct autofs_ctx *actx) +{ + autofs_orphan_maps(actx); + + return EOK; +} + +static void +autofs_maps_delete_cb(hash_entry_t *item, + hash_destroy_enum deltype, + void *pvt) +{ + struct autofs_ctx *autofs_ctx; + struct autofs_enum_ctx *enum_ctx; + + autofs_ctx = talloc_get_type(pvt, struct autofs_ctx); + enum_ctx = talloc_get_type(item->value.ptr, struct autofs_enum_ctx); + + talloc_unlink(autofs_ctx->maps, enum_ctx); +} + +static errno_t +autofs_register_service_iface(struct autofs_ctx *autofs_ctx, + struct resp_ctx *rctx) +{ + errno_t ret; + + SBUS_INTERFACE(iface_svc, + sssd_service, + SBUS_METHODS( + SBUS_SYNC(METHOD, sssd_service, rotateLogs, responder_logrotate, rctx), + SBUS_SYNC(METHOD, sssd_service, clearEnumCache, autofs_clean_hash_table, autofs_ctx) + ), + SBUS_SIGNALS(SBUS_NO_SIGNALS), + SBUS_PROPERTIES( + SBUS_SYNC(GETTER, sssd_service, debug_level, generic_get_debug_level, NULL), + SBUS_SYNC(SETTER, sssd_service, debug_level, generic_set_debug_level, NULL) + ) + ); + + ret = sbus_connection_add_path(rctx->mon_conn, SSS_BUS_PATH, &iface_svc); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to register service interface" + "[%d]: %s\n", ret, sss_strerror(ret)); + } + + return ret; +} + +static int +autofs_process_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct confdb_ctx *cdb) +{ + struct resp_ctx *rctx; + struct sss_cmd_table *autofs_cmds; + struct autofs_ctx *autofs_ctx; + int ret; + + autofs_cmds = get_autofs_cmds(); + ret = sss_process_init(mem_ctx, ev, cdb, + autofs_cmds, + SSS_AUTOFS_SOCKET_NAME, -1, NULL, -1, + CONFDB_AUTOFS_CONF_ENTRY, + SSS_BUS_AUTOFS, SSS_AUTOFS_SBUS_SERVICE_NAME, + autofs_connection_setup, + &rctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "sss_process_init() failed\n"); + return ret; + } + + autofs_ctx = talloc_zero(rctx, struct autofs_ctx); + if (!autofs_ctx) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error initializing autofs_ctx\n"); + ret = ENOMEM; + goto fail; + } + + ret = autofs_get_config(autofs_ctx, cdb); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Cannot read autofs configuration\n"); + goto fail; + } + + autofs_ctx->rctx = rctx; + autofs_ctx->rctx->pvt_ctx = autofs_ctx; + + /* Create the lookup table for setautomntent results */ + autofs_ctx->maps = sss_ptr_hash_create(autofs_ctx, + autofs_maps_delete_cb, + autofs_ctx); + if (autofs_ctx->maps == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to initialize automount maps hash table\n"); + ret = EIO; + goto fail; + } + + ret = schedule_get_domains_task(rctx, rctx->ev, rctx, NULL, NULL, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "schedule_get_domains_tasks failed.\n"); + goto fail; + } + + /* The responder is initialized. Now tell it to the monitor. */ + ret = sss_monitor_service_init(rctx, rctx->ev, SSS_BUS_AUTOFS, + SSS_AUTOFS_SBUS_SERVICE_NAME, + SSS_AUTOFS_SBUS_SERVICE_VERSION, + MT_SVC_SERVICE, + &rctx->last_request_time, &rctx->mon_conn); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error setting up message bus\n"); + goto fail; + } + + ret = autofs_register_service_iface(autofs_ctx, rctx); + if (ret != EOK) { + goto fail; + } + + DEBUG(SSSDBG_TRACE_FUNC, "autofs Initialization complete\n"); + return EOK; + +fail: + talloc_free(rctx); + return ret; +} + +int main(int argc, const char *argv[]) +{ + int opt; + poptContext pc; + char *opt_logger = NULL; + struct main_context *main_ctx; + int ret; + uid_t uid = 0; + gid_t gid = 0; + + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_MAIN_OPTS + SSSD_LOGGER_OPTS + SSSD_SERVER_OPTS(uid, gid) + SSSD_RESPONDER_OPTS + POPT_TABLEEND + }; + + /* Set debug level to invalid value so we can decide if -d 0 was used. */ + debug_level = SSSDBG_INVALID; + + umask(DFL_RSP_UMASK); + + pc = poptGetContext(argv[0], argc, argv, long_options, 0); + while((opt = poptGetNextOpt(pc)) != -1) { + switch(opt) { + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + return 1; + } + } + + poptFreeContext(pc); + + /* set up things like debug, signals, daemonization, etc. */ + debug_log_file = "sssd_autofs"; + DEBUG_INIT(debug_level, opt_logger); + + ret = server_setup("autofs", true, 0, uid, gid, + CONFDB_AUTOFS_CONF_ENTRY, &main_ctx, true); + if (ret != EOK) { + return 2; + } + + ret = die_if_parent_died(); + if (ret != EOK) { + /* This is not fatal, don't return */ + DEBUG(SSSDBG_OP_FAILURE, "Could not set up to exit " + "when parent process does\n"); + } + + ret = autofs_process_init(main_ctx, + main_ctx->event_ctx, + main_ctx->confdb_ctx); + if (ret != EOK) { + return 3; + } + + /* loop on main */ + server_loop(main_ctx); + + return 0; +} diff --git a/src/responder/autofs/autofssrv_cmd.c b/src/responder/autofs/autofssrv_cmd.c new file mode 100644 index 0000000..7c80909 --- /dev/null +++ b/src/responder/autofs/autofssrv_cmd.c @@ -0,0 +1,961 @@ +/* + Authors: + Jakub Hrozek <jhrozek@redhat.com> + + Copyright (C) 2012 Red Hat + + Autofs responder: commands + + 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 <talloc.h> + +#include "util/util.h" +#include "responder/common/responder.h" +#include "responder/common/responder_packet.h" +#include "responder/common/cache_req/cache_req.h" +#include "responder/autofs/autofs_private.h" +#include "db/sysdb.h" +#include "db/sysdb_autofs.h" +#include "confdb/confdb.h" +#include "sss_iface/sss_iface_async.h" +#include "util/sss_ptr_hash.h" + +static int autofs_cmd_send_error(struct autofs_cmd_ctx *cmdctx, int err) +{ + return sss_cmd_send_error(cmdctx->cli_ctx, err); +} + +static int +autofs_cmd_send_empty(struct autofs_cmd_ctx *cmdctx) +{ + return sss_cmd_send_empty(cmdctx->cli_ctx); +} + +static int +autofs_cmd_done(struct autofs_cmd_ctx *cmdctx, int ret) +{ + switch (ret) { + case EOK: + /* all fine, just return here */ + break; + + case ENOENT: + ret = autofs_cmd_send_empty(cmdctx); + if (ret) { + return EFAULT; + } + sss_cmd_done(cmdctx->cli_ctx, cmdctx); + break; + + case EAGAIN: + /* async processing, just return here */ + break; + + case EFAULT: + /* very bad error */ + return EFAULT; + + default: + ret = autofs_cmd_send_error(cmdctx, ret); + if (ret) { + return EFAULT; + } + sss_cmd_done(cmdctx->cli_ctx, cmdctx); + break; + } + + return EOK; +} + +static errno_t +autofs_fill_entry(struct ldb_message *entry, struct sss_packet *packet, size_t *rp) +{ + errno_t ret; + const char *key; + size_t keylen; + const char *value; + size_t valuelen; + uint8_t *body; + size_t blen; + size_t len; + + key = ldb_msg_find_attr_as_string(entry, SYSDB_AUTOFS_ENTRY_KEY, NULL); + value = ldb_msg_find_attr_as_string(entry, SYSDB_AUTOFS_ENTRY_VALUE, NULL); + if (!key || !value) { + DEBUG(SSSDBG_MINOR_FAILURE, "Incomplete entry\n"); + return EINVAL; + } + + keylen = 1 + strlen(key); + valuelen = 1 + strlen(value); + len = sizeof(uint32_t) + sizeof(uint32_t) + keylen + sizeof(uint32_t) + valuelen; + + ret = sss_packet_grow(packet, len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot grow packet\n"); + return ret; + } + + sss_packet_get_body(packet, &body, &blen); + + SAFEALIGN_SET_UINT32(&body[*rp], len, rp); + SAFEALIGN_SET_UINT32(&body[*rp], keylen, rp); + + if (keylen == 1) { + body[*rp] = '\0'; + } else { + memcpy(&body[*rp], key, keylen); + } + *rp += keylen; + + SAFEALIGN_SET_UINT32(&body[*rp], valuelen, rp); + if (valuelen == 1) { + body[*rp] = '\0'; + } else { + memcpy(&body[*rp], value, valuelen); + } + *rp += valuelen; + + return EOK; +} + +void +autofs_orphan_maps(struct autofs_ctx *autofs_ctx) +{ + /* It will automatically decrease the refcount of enum_ctx through + * delete callback. */ + sss_ptr_hash_delete_all(autofs_ctx->maps, false); +} + +static void +autofs_enumctx_lifetime_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *pvt) +{ + struct autofs_enum_ctx *enum_ctx; + + enum_ctx = talloc_get_type(pvt, struct autofs_enum_ctx); + + /* Remove it from the table. It will automatically decrease the refcount. */ + sss_ptr_hash_delete(enum_ctx->table, enum_ctx->key, false); +} + +static void +autofs_set_enumctx_lifetime(struct autofs_ctx *autofs_ctx, + struct autofs_enum_ctx *enum_ctx, + uint32_t lifetime) +{ + struct timeval tv; + struct tevent_timer *te; + + tv = tevent_timeval_current_ofs(lifetime, 0); + te = tevent_add_timer(autofs_ctx->rctx->ev, enum_ctx, tv, + autofs_enumctx_lifetime_timeout, enum_ctx); + if (te == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not set up life timer for autofs maps. " + "Entries may become stale.\n"); + } +} + +static struct autofs_enum_ctx * +autofs_create_enumeration_context(TALLOC_CTX *mem_ctx, + struct autofs_ctx *autofs_ctx, + const char *mapname) +{ + struct autofs_enum_ctx *enum_ctx; + errno_t ret; + + enum_ctx = talloc_zero(mem_ctx, struct autofs_enum_ctx); + if (enum_ctx == NULL) { + return NULL; + } + + enum_ctx->ready = false; + enum_ctx->table = autofs_ctx->maps; + + enum_ctx->key = talloc_strdup(enum_ctx, mapname); + if (enum_ctx->key == NULL) { + talloc_free(enum_ctx); + return NULL; + } + + ret = sss_ptr_hash_add(autofs_ctx->maps, mapname, + enum_ctx, struct autofs_enum_ctx); + if (ret != EOK) { + talloc_free(enum_ctx); + return NULL; + } + + return enum_ctx; +} + +static void +autofs_orphan_master_map(struct autofs_ctx *autofs_ctx, + const char *mapname) +{ + struct sss_domain_info *dom; + errno_t ret; + + if (strcmp(mapname, "auto.master") != 0) { + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Invalidating master map\n"); + + /* Remove and invalidate all maps. */ + autofs_orphan_maps(autofs_ctx); + + DEBUG(SSSDBG_TRACE_FUNC, "Invalidating autofs maps\n"); + for (dom = autofs_ctx->rctx->domains; + dom != NULL; + dom = get_next_domain(dom, SSS_GND_DESCEND)) { + ret = sysdb_invalidate_autofs_maps(dom); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to invalidate maps in " + "%s [%d]: %s\n", dom->name, ret, sss_strerror(ret)); + } + } +} + +struct autofs_setent_state { + struct autofs_ctx *autofs_ctx; + struct autofs_enum_ctx *enum_ctx; +}; + +static void autofs_setent_done(struct tevent_req *subreq); + +static struct tevent_req * +autofs_setent_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct autofs_ctx *autofs_ctx, + const char *mapname) +{ + struct autofs_setent_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct autofs_setent_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + state->autofs_ctx = autofs_ctx; + + /* Lookup current results if available. */ + state->enum_ctx = sss_ptr_hash_lookup(autofs_ctx->maps, mapname, + struct autofs_enum_ctx); + if (state->enum_ctx != NULL) { + if (state->enum_ctx->ready) { + ret = EOK; + goto done; + } + + /* Map is still being created. We will watch the request. */ + ret = setent_add_ref(state, &state->enum_ctx->notify_list, req); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to watch enumeration request " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + ret = EAGAIN; + goto done; + } + + /* Map does not yet exist. Create the enumeration object and fetch data. */ + state->enum_ctx = autofs_create_enumeration_context(state, autofs_ctx, mapname); + if (state->enum_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create enumeration context!\n"); + ret = ENOMEM; + goto done; + } + + subreq = cache_req_autofs_map_entries_send(mem_ctx, ev, autofs_ctx->rctx, + autofs_ctx->rctx->ncache, + 0, NULL, mapname); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, autofs_setent_done, req); + + ret = EAGAIN; + +done: + if (ret == EOK) { + tevent_req_done(req); + tevent_req_post(req, ev); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void autofs_setent_done(struct tevent_req *subreq) +{ + struct autofs_setent_state *state; + struct cache_req_result *result; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct autofs_setent_state); + + ret = cache_req_autofs_map_entries_recv(state, subreq, &result); + talloc_zfree(subreq); + + switch (ret) { + case EOK: + state->enum_ctx->found = true; + state->enum_ctx->result = talloc_steal(state->enum_ctx, result); + autofs_set_enumctx_lifetime(state->autofs_ctx, state->enum_ctx, + state->enum_ctx->result->domain->autofsmap_timeout); + break; + case ENOENT: + state->enum_ctx->found = false; + state->enum_ctx->result = NULL; + autofs_set_enumctx_lifetime(state->autofs_ctx, state->enum_ctx, + state->autofs_ctx->neg_timeout); + break; + default: + DEBUG(SSSDBG_OP_FAILURE, "Unable to get map data [%d]: %s\n", + ret, sss_strerror(ret)); + + setent_notify(&state->enum_ctx->notify_list, ret); + talloc_zfree(state->enum_ctx); + tevent_req_error(req, ret); + return; + } + + state->enum_ctx->ready = true; + + /* Make the enumeration context disappear with maps table. */ + talloc_steal(state->autofs_ctx->maps, state->enum_ctx); + + setent_notify_done(&state->enum_ctx->notify_list); + tevent_req_done(req); + return; +} + +static errno_t +autofs_setent_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct autofs_enum_ctx **_enum_ctx) +{ + struct autofs_setent_state *state; + state = tevent_req_data(req, struct autofs_setent_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_enum_ctx = talloc_reference(mem_ctx, state->enum_ctx); + + return EOK; +} + +static errno_t +autofs_read_setautomntent_input(struct cli_ctx *cli_ctx, + const char **_mapname) +{ + struct cli_protocol *pctx; + uint8_t *body; + size_t blen; + + pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol); + + sss_packet_get_body(pctx->creq->in, &body, &blen); + + /* if not terminated fail */ + if (body[blen - 1] != '\0') { + return EINVAL; + } + + /* If the body isn't valid UTF-8, fail */ + if (!sss_utf8_check(body, blen - 1)) { + return EINVAL; + } + + *_mapname = (const char *)body; + + return EOK; +} + +static errno_t +autofs_write_setautomntent_output(struct cli_ctx *cli_ctx, + struct cache_req_result *result) +{ + struct cli_protocol *pctx; + uint8_t *body; + size_t blen; + errno_t ret; + + pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol); + + ret = sss_packet_new(pctx->creq, 0, sss_packet_get_cmd(pctx->creq->in), + &pctx->creq->out); + if (ret != EOK) { + return ret; + } + + if (result == NULL || result->count == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "Map was not found\n"); + return sss_cmd_empty_packet(pctx->creq->out); + } + + DEBUG(SSSDBG_TRACE_FUNC, "Map found\n"); + + ret = sss_packet_grow(pctx->creq->out, 2 * sizeof(uint32_t)); + if (ret != EOK) { + return ret; + } + + sss_packet_get_body(pctx->creq->out, &body, &blen); + + /* Got some results */ + SAFEALIGN_SETMEM_UINT32(body, 1, NULL); + + /* Reserved padding */ + SAFEALIGN_SETMEM_UINT32(body + sizeof(uint32_t), 0, NULL); + + return EOK; +} + +static void +sss_autofs_cmd_setautomntent_done(struct tevent_req *req); + +static int +sss_autofs_cmd_setautomntent(struct cli_ctx *cli_ctx) +{ + struct autofs_cmd_ctx *cmd_ctx; + struct autofs_ctx *autofs_ctx; + struct tevent_req *req; + errno_t ret; + + autofs_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct autofs_ctx); + + cmd_ctx = talloc_zero(cli_ctx, struct autofs_cmd_ctx); + if (cmd_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create command context.\n"); + return ENOMEM; + } + + cmd_ctx->cli_ctx = cli_ctx; + cmd_ctx->autofs_ctx = autofs_ctx; + + ret = autofs_read_setautomntent_input(cli_ctx, &cmd_ctx->mapname); + if (ret != EOK) { + goto done; + } + + autofs_orphan_master_map(autofs_ctx, cmd_ctx->mapname); + + DEBUG(SSSDBG_TRACE_FUNC, "Obtaining autofs map %s\n", + cmd_ctx->mapname); + + req = cache_req_autofs_map_by_name_send(cli_ctx, cli_ctx->ev, + autofs_ctx->rctx, + autofs_ctx->rctx->ncache, 0, NULL, + cmd_ctx->mapname); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "cache_req_autofs_map_by_name_send failed\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(req, sss_autofs_cmd_setautomntent_done, cmd_ctx); + + ret = EOK; + +done: + return autofs_cmd_done(cmd_ctx, ret); +} + +static void +sss_autofs_cmd_setautomntent_done(struct tevent_req *req) +{ + struct cache_req_result *result; + struct autofs_cmd_ctx *cmd_ctx; + errno_t ret; + + cmd_ctx = tevent_req_callback_data(req, struct autofs_cmd_ctx); + + ret = cache_req_autofs_map_by_name_recv(cmd_ctx, req, &result); + talloc_zfree(req); + if (ret != EOK) { + autofs_cmd_done(cmd_ctx, ret); + return; + } + + ret = autofs_write_setautomntent_output(cmd_ctx->cli_ctx, result); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create reply packet " + "[%d]: %s\n", ret, sss_strerror(ret)); + autofs_cmd_done(cmd_ctx, ret); + return; + } + + sss_cmd_done(cmd_ctx->cli_ctx, NULL); +} + +static int +sss_autofs_cmd_endautomntent(struct cli_ctx *client) +{ + struct cli_protocol *pctx; + errno_t ret; + + DEBUG(SSSDBG_TRACE_FUNC, "endautomntent called\n"); + + pctx = talloc_get_type(client->protocol_ctx, struct cli_protocol); + + /* create response packet */ + ret = sss_packet_new(pctx->creq, 0, + sss_packet_get_cmd(pctx->creq->in), + &pctx->creq->out); + + if (ret != EOK) { + return ret; + } + + sss_cmd_done(client, NULL); + return EOK; +} + +static errno_t +autofs_read_getautomntent_input(struct cli_ctx *cli_ctx, + const char **_mapname, + uint32_t *_cursor, + uint32_t *_max_entries) +{ + struct cli_protocol *pctx; + const char *mapname; + uint32_t namelen; + uint8_t *body; + size_t blen; + size_t c = 0; + + pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol); + + sss_packet_get_body(pctx->creq->in, &body, &blen); + + SAFEALIGN_COPY_UINT32_CHECK(&namelen, body+c, blen, &c); + if (namelen == 0 || namelen > blen - c) { + return EINVAL; + } + + mapname = (const char *)body + c; + + /* if not null-terminated fail */ + if (mapname[namelen] != '\0') { + return EINVAL; + } + + /* If the name isn't valid UTF-8, fail */ + if (!sss_utf8_check((const uint8_t *)mapname, namelen - 1)) { + return EINVAL; + } + + SAFEALIGN_COPY_UINT32_CHECK(_cursor, body + c + namelen + 1, blen, &c); + SAFEALIGN_COPY_UINT32_CHECK(_max_entries, body + c + namelen + 1, blen, &c); + *_mapname = mapname; + + return EOK; +} + +static errno_t +autofs_write_getautomntent_output(struct cli_ctx *cli_ctx, + struct autofs_enum_ctx *enum_ctx, + uint32_t cursor, + uint32_t max_entries) +{ + struct cli_protocol *pctx; + struct ldb_message **entries; + struct ldb_message *entry; + size_t count; + size_t num_entries; + uint8_t *body; + size_t blen; + size_t rp; + uint32_t i; + uint32_t stop; + uint32_t left; + errno_t ret; + + pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol); + + count = enum_ctx->found ? enum_ctx->result->count - 1 : 0; + entries = count > 0 ? enum_ctx->result->msgs + 1 : NULL; + + ret = sss_packet_new(pctx->creq, 0, sss_packet_get_cmd(pctx->creq->in), + &pctx->creq->out); + if (ret != EOK) { + return ret; + } + + if (!enum_ctx->found || count == 0 || cursor >= count) { + DEBUG(SSSDBG_TRACE_FUNC, "No entries was not found\n"); + return sss_cmd_empty_packet(pctx->creq->out); + } + + /* allocate memory for number of entries in the packet */ + ret = sss_packet_grow(pctx->creq->out, sizeof(uint32_t)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot grow packet\n"); + return ret; + } + + rp = sizeof(uint32_t); /* We will first write the elements. */ + left = count - cursor; + stop = max_entries < left ? max_entries : left; + + num_entries = 0; + for (i = 0; i < stop; i++) { + entry = entries[cursor]; + cursor++; + + ret = autofs_fill_entry(entry, pctx->creq->out, &rp); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot fill entry %d/%d, skipping\n", i, stop); + continue; + } + num_entries++; + } + + /* packet grows in fill_autofs_entry, body pointer may change, + * thus we have to obtain it here */ + sss_packet_get_body(pctx->creq->out, &body, &blen); + + rp = 0; + SAFEALIGN_SET_UINT32(&body[rp], num_entries, &rp); + + return EOK; +} + +static void +sss_autofs_cmd_getautomntent_done(struct tevent_req *req); + +static int +sss_autofs_cmd_getautomntent(struct cli_ctx *cli_ctx) +{ + struct autofs_cmd_ctx *cmd_ctx; + struct autofs_ctx *autofs_ctx; + struct tevent_req *req; + errno_t ret; + + autofs_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct autofs_ctx); + + cmd_ctx = talloc_zero(cli_ctx, struct autofs_cmd_ctx); + if (cmd_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create command context.\n"); + return ENOMEM; + } + + cmd_ctx->cli_ctx = cli_ctx; + cmd_ctx->autofs_ctx = autofs_ctx; + + ret = autofs_read_getautomntent_input(cli_ctx, &cmd_ctx->mapname, + &cmd_ctx->cursor, + &cmd_ctx->max_entries); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Obtaining enumeration context for %s\n", + cmd_ctx->mapname); + + req = autofs_setent_send(cli_ctx, cli_ctx->ev, autofs_ctx, cmd_ctx->mapname); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "autofs_setent_send failed\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(req, sss_autofs_cmd_getautomntent_done, cmd_ctx); + + ret = EOK; + +done: + return autofs_cmd_done(cmd_ctx, ret); +} + +static void +sss_autofs_cmd_getautomntent_done(struct tevent_req *req) +{ + struct autofs_enum_ctx *enum_ctx; + struct autofs_cmd_ctx *cmd_ctx; + errno_t ret; + + cmd_ctx = tevent_req_callback_data(req, struct autofs_cmd_ctx); + + ret = autofs_setent_recv(cmd_ctx, req, &enum_ctx); + talloc_zfree(req); + if (ret != EOK) { + autofs_cmd_done(cmd_ctx, ret); + return; + } + + ret = autofs_write_getautomntent_output(cmd_ctx->cli_ctx, enum_ctx, + cmd_ctx->cursor, + cmd_ctx->max_entries); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create reply packet " + "[%d]: %s\n", ret, sss_strerror(ret)); + autofs_cmd_done(cmd_ctx, ret); + return; + } + + sss_cmd_done(cmd_ctx->cli_ctx, NULL); +} + +static errno_t +autofs_read_getautomntbyname_input(struct cli_ctx *cli_ctx, + const char **_mapname, + const char **_keyname) +{ + struct cli_protocol *pctx; + const char *mapname; + const char *keyname; + uint32_t namelen; + uint32_t keylen; + uint8_t *body; + size_t blen; + size_t c = 0; + + pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol); + + sss_packet_get_body(pctx->creq->in, &body, &blen); + + /* Get map name. */ + SAFEALIGN_COPY_UINT32_CHECK(&namelen, body + c, blen, &c); + if (namelen == 0 || namelen > blen - c) { + return EINVAL; + } + + mapname = (const char *) body + c; + + /* if not null-terminated fail */ + if (mapname[namelen] != '\0') { + return EINVAL; + } + + /* If the name isn't valid UTF-8, fail */ + if (!sss_utf8_check((const uint8_t *)mapname, namelen - 1)) { + return EINVAL; + } + + c += namelen + 1; + + /* Get key name. */ + SAFEALIGN_COPY_UINT32_CHECK(&keylen, body + c, blen, &c); + if (keylen == 0 || keylen > blen - c) { + return EINVAL; + } + + keyname = (const char *) body + c; + + /* if not null-terminated fail */ + if (keyname[keylen] != '\0') { + return EINVAL; + } + + /* If the key isn't valid UTF-8, fail */ + if (!sss_utf8_check((const uint8_t *)keyname, keylen - 1)) { + return EINVAL; + } + + *_mapname = mapname; + *_keyname = keyname; + + return EOK; +} + +static errno_t +autofs_write_getautomntbyname_output(struct cli_ctx *cli_ctx, + struct cache_req_result *result, + const char *keyname) +{ + struct cli_protocol *pctx; + struct ldb_message *entry; + const char *value; + size_t value_len; + size_t len; + uint8_t *body; + size_t blen; + size_t rp; + errno_t ret; + + pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol); + + if (result == NULL || result->count == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "Key [%s] was not found\n", keyname); + return sss_cmd_empty_packet(pctx->creq->out); + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Found key [%s]\n", keyname); + entry = result->msgs[0]; + + ret = sss_packet_new(pctx->creq, 0, sss_packet_get_cmd(pctx->creq->in), + &pctx->creq->out); + if (ret != EOK) { + return ret; + } + + value = ldb_msg_find_attr_as_string(entry, SYSDB_AUTOFS_ENTRY_VALUE, NULL); + if (value == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No entry value found in [%s]\n", keyname); + return EINVAL; + } + + value_len = 1 + strlen(value); + len = sizeof(uint32_t) + sizeof(uint32_t) + value_len; + + ret = sss_packet_grow(pctx->creq->out, len); + if (ret != EOK) { + return ret; + } + + sss_packet_get_body(pctx->creq->out, &body, &blen); + + rp = 0; + SAFEALIGN_SET_UINT32(&body[rp], len, &rp); + + SAFEALIGN_SET_UINT32(&body[rp], value_len, &rp); + if (value_len == 1) { + body[rp] = '\0'; + } else { + memcpy(&body[rp], value, value_len); + } + + return EOK; +} + +static void +sss_autofs_cmd_getautomntbyname_done(struct tevent_req *req); + +static int +sss_autofs_cmd_getautomntbyname(struct cli_ctx *cli_ctx) +{ + struct autofs_cmd_ctx *cmd_ctx; + struct autofs_ctx *autofs_ctx; + struct tevent_req *req; + errno_t ret; + + autofs_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct autofs_ctx); + + cmd_ctx = talloc_zero(cli_ctx, struct autofs_cmd_ctx); + if (cmd_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create command context.\n"); + return ENOMEM; + } + + cmd_ctx->cli_ctx = cli_ctx; + cmd_ctx->autofs_ctx = autofs_ctx; + + ret = autofs_read_getautomntbyname_input(cli_ctx, &cmd_ctx->mapname, + &cmd_ctx->keyname); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Obtaining autofs entry %s:%s\n", + cmd_ctx->mapname, cmd_ctx->keyname); + + req = cache_req_autofs_entry_by_name_send(cli_ctx, cli_ctx->ev, + autofs_ctx->rctx, + autofs_ctx->rctx->ncache, 0, NULL, + cmd_ctx->mapname, + cmd_ctx->keyname); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "cache_req_autofs_entry_by_name_send failed\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(req, sss_autofs_cmd_getautomntbyname_done, cmd_ctx); + + ret = EOK; + +done: + return autofs_cmd_done(cmd_ctx, ret); +} + +static void +sss_autofs_cmd_getautomntbyname_done(struct tevent_req *req) +{ + struct cache_req_result *result; + struct autofs_cmd_ctx *cmd_ctx; + errno_t ret; + + cmd_ctx = tevent_req_callback_data(req, struct autofs_cmd_ctx); + + ret = cache_req_autofs_entry_by_name_recv(cmd_ctx, req, &result); + talloc_zfree(req); + if (ret != EOK) { + autofs_cmd_done(cmd_ctx, ret); + return; + } + + ret = autofs_write_getautomntbyname_output(cmd_ctx->cli_ctx, result, + cmd_ctx->keyname); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create reply packet " + "[%d]: %s\n", ret, sss_strerror(ret)); + autofs_cmd_done(cmd_ctx, ret); + return; + } + + sss_cmd_done(cmd_ctx->cli_ctx, NULL); +} + +struct cli_protocol_version *register_cli_protocol_version(void) +{ + static struct cli_protocol_version autofs_cli_protocol_version[] = { + { SSS_AUTOFS_PROTO_VERSION, NULL, NULL } + }; + + return autofs_cli_protocol_version; +} + +struct sss_cmd_table *get_autofs_cmds(void) +{ + static struct sss_cmd_table autofs_cmds[] = { + { SSS_GET_VERSION, sss_cmd_get_version }, + { SSS_AUTOFS_SETAUTOMNTENT, sss_autofs_cmd_setautomntent }, + { SSS_AUTOFS_GETAUTOMNTENT, sss_autofs_cmd_getautomntent }, + { SSS_AUTOFS_GETAUTOMNTBYNAME, sss_autofs_cmd_getautomntbyname }, + { SSS_AUTOFS_ENDAUTOMNTENT, sss_autofs_cmd_endautomntent }, + { SSS_CLI_NULL, NULL} + }; + + return autofs_cmds; +} + +int autofs_connection_setup(struct cli_ctx *cctx) +{ + int ret; + + ret = sss_connection_setup(cctx); + if (ret != EOK) return ret; + + return EOK; +} diff --git a/src/responder/common/cache_req/cache_req.c b/src/responder/common/cache_req/cache_req.c new file mode 100644 index 0000000..b827595 --- /dev/null +++ b/src/responder/common/cache_req/cache_req.c @@ -0,0 +1,1612 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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 <ldb.h> +#include <talloc.h> +#include <tevent.h> +#include <errno.h> + +#include "util/util.h" +#include "util/sss_chain_id.h" +#include "responder/common/responder.h" +#include "responder/common/cache_req/cache_req_private.h" +#include "responder/common/cache_req/cache_req_plugin.h" + +static const struct cache_req_plugin * +cache_req_get_plugin(enum cache_req_type type) +{ + static const struct cache_req_plugin *plugins[CACHE_REQ_SENTINEL] = { + &cache_req_user_by_name, + &cache_req_user_by_upn, + &cache_req_user_by_id, + &cache_req_user_by_cert, + &cache_req_user_by_filter, + + &cache_req_group_by_name, + &cache_req_group_by_id, + &cache_req_group_by_filter, + + &cache_req_initgroups_by_name, + &cache_req_initgroups_by_upn, + +#ifdef BUILD_SUBID + &cache_req_subid_ranges_by_name, +#endif + + &cache_req_object_by_sid, + &cache_req_object_by_name, + &cache_req_object_by_id, + + &cache_req_enum_users, + &cache_req_enum_groups, + &cache_req_enum_svc, + &cache_req_enum_ip_hosts, + &cache_req_enum_ip_networks, + + &cache_req_svc_by_name, + &cache_req_svc_by_port, + + &cache_req_netgroup_by_name, + + &cache_req_ssh_host_id_by_name, + + &cache_req_autofs_map_entries, + &cache_req_autofs_map_by_name, + &cache_req_autofs_entry_by_name, + + &cache_req_ip_host_by_name, + &cache_req_ip_host_by_addr, + &cache_req_ip_network_by_name, + &cache_req_ip_network_by_addr, + }; + + if (type >= CACHE_REQ_SENTINEL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Bug: invalid plugin type!"); + return NULL; + } + + return plugins[type]; +} + +static errno_t cache_req_set_plugin(struct cache_req *cr, + enum cache_req_type type) +{ + const struct cache_req_plugin *plugin; + + plugin = cache_req_get_plugin(type); + if (plugin == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Bug: unset plugin!"); + return EINVAL; + } + + cr->reqname = plugin->name; + cr->plugin = plugin; + + CACHE_REQ_DEBUG(SSSDBG_TRACE_INTERNAL, cr, "Setting \"%s\" plugin\n", + plugin->name); + + return EOK; +} + +static const char * +cache_req_dom_type_as_str(struct cache_req *cr) +{ + if (cr == NULL) { + return "BUG: Invalid cache_req pointer\n"; + } + switch (cr->req_dom_type) { + case CACHE_REQ_POSIX_DOM: + return "POSIX-only"; + case CACHE_REQ_APPLICATION_DOM: + return "Application-only"; + case CACHE_REQ_ANY_DOM: + return "Any"; + } + + return "Unknown"; +} + +static struct cache_req * +cache_req_create(TALLOC_CTX *mem_ctx, + struct resp_ctx *rctx, + struct cache_req_data *data, + struct sss_nc_ctx *ncache, + int midpoint, + enum cache_req_dom_type req_dom_type) +{ + struct cache_req *cr; + bool bypass_cache; + errno_t ret; + + cr = talloc_zero(mem_ctx, struct cache_req); + if (cr == NULL) { + return NULL; + } + + cr->rctx = rctx; + cr->data = data; + cr->ncache = ncache; + cr->midpoint = midpoint; + cr->req_dom_type = req_dom_type; + cr->req_start = time(NULL); + + /* It is perfectly fine to just overflow here. */ + cr->reqid = rctx->cache_req_num++; + + ret = cache_req_set_plugin(cr, data->type); + if (ret != EOK) { + talloc_free(cr); + return NULL; + } + + bypass_cache = cr->plugin->bypass_cache || cr->data->bypass_cache; + if (bypass_cache && cr->data->bypass_dp) { + CACHE_REQ_DEBUG(SSSDBG_CRIT_FAILURE, cr, + "Cannot bypass cache and dp at the same time!"); + talloc_free(cr); + return NULL; + } + if (rctx->cache_first) { + cr->cache_behavior = CACHE_REQ_CACHE_FIRST; + } + /* it is ok to override cache_first here */ + if (bypass_cache) { + cr->cache_behavior = CACHE_REQ_BYPASS_CACHE; + } else if (cr->data->bypass_dp) { + cr->cache_behavior = CACHE_REQ_BYPASS_PROVIDER; + } + + return cr; +} + +static errno_t +cache_req_set_name(struct cache_req *cr, const char *name) +{ + const char *dup_name; + + CACHE_REQ_DEBUG(SSSDBG_TRACE_FUNC, cr, "Setting name [%s]\n", name); + + dup_name = talloc_strdup(cr->data, name); + if (dup_name == NULL) { + CACHE_REQ_DEBUG(SSSDBG_CRIT_FAILURE, cr, "Unable to set name!\n"); + return ENOMEM; + } + + talloc_zfree(cr->data->name.name); + cr->data->name.name = dup_name; + + return EOK; +} + +static bool +cache_req_validate_domain_enumeration(struct cache_req *cr, + struct sss_domain_info *domain) +{ + if (!cr->plugin->require_enumeration) { + return true; + } + + if (domain->enumerate == false) { + CACHE_REQ_DEBUG(SSSDBG_TRACE_FUNC, cr, "Domain %s does not support " + "enumeration, skipping...\n", domain->name); + if (cr->rctx->enumeration_warn_logged == false) { + sss_log(SSS_LOG_NOTICE, "Enumeration requested but not enabled\n"); + CACHE_REQ_DEBUG(SSSDBG_TRACE_FUNC, cr, + "Enumeration requested but not enabled\n"); + cr->rctx->enumeration_warn_logged = true; + } + return false; + } + + CACHE_REQ_DEBUG(SSSDBG_TRACE_FUNC, cr, "Domain %s supports enumeration\n", + domain->name); + + return true; +} + +static bool +cache_req_validate_domain_type(struct cache_req *cr, + struct sss_domain_info *domain) +{ + bool valid = false; + + switch (cr->req_dom_type) { + case CACHE_REQ_POSIX_DOM: + valid = domain->type == DOM_TYPE_POSIX ? true : false; + break; + case CACHE_REQ_APPLICATION_DOM: + valid = domain->type == DOM_TYPE_APPLICATION ? true : false; + break; + case CACHE_REQ_ANY_DOM: + valid = true; + break; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Request type %s for domain %s type %s is %svalid\n", + cache_req_dom_type_as_str(cr), + domain->name, + sss_domain_type_str(domain), + valid ? "" : "not "); + return valid; +} + +static bool +cache_req_validate_domain(struct cache_req *cr, + struct sss_domain_info *domain) +{ + bool ok; + + ok = cache_req_validate_domain_enumeration(cr, domain); + if (ok == false) { + return false; + } + + ok = cache_req_validate_domain_type(cr, domain); + if (ok == false) { + return false; + } + + ok = !cr->data->hybrid_lookup || domain->mpg_mode == MPG_HYBRID; + if (ok == false) { + return false; + } + + return true; +} + +static errno_t +cache_req_is_well_known_object(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_result **_result) +{ + errno_t ret; + + if (cr->plugin->is_well_known_fn == NULL) { + return ENOENT; + } + + ret = cr->plugin->is_well_known_fn(mem_ctx, cr, cr->data, _result); + if (ret == EOK) { + CACHE_REQ_DEBUG(SSSDBG_TRACE_FUNC, cr, "Object is well known!\n"); + (*_result)->well_known_object = true; + } else if (ret != ENOENT) { + CACHE_REQ_DEBUG(SSSDBG_CRIT_FAILURE, cr, + "Unable to prepare data [%d]: %s\n", + ret, sss_strerror(ret)); + } + + return ret; +} + +static errno_t +cache_req_prepare_domain_data(struct cache_req *cr, + struct sss_domain_info *domain) +{ + errno_t ret; + + if (cr->plugin->prepare_domain_data_fn == NULL) { + return EOK; + } + + CACHE_REQ_DEBUG(SSSDBG_TRACE_FUNC, cr, + "Preparing input data for domain [%s] rules\n", + domain->name); + + ret = cr->plugin->prepare_domain_data_fn(cr, cr->data, domain); + if (ret != EOK) { + CACHE_REQ_DEBUG(SSSDBG_CRIT_FAILURE, cr, + "Unable to prepare data [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + return EOK; +} + +static errno_t +cache_req_create_debug_name(struct cache_req *cr, + struct sss_domain_info *domain) +{ + if (cr->plugin->create_debug_name_fn == NULL) { + CACHE_REQ_DEBUG(SSSDBG_CRIT_FAILURE, cr, + "Bug: no create debug name function specified!\n"); + return ERR_INTERNAL; + } + + talloc_zfree(cr->debugobj); + + cr->debugobj = cr->plugin->create_debug_name_fn(cr, cr->data, domain); + if (cr->debugobj == NULL) { + CACHE_REQ_DEBUG(SSSDBG_CRIT_FAILURE, cr, + "Unable to create debug name!\n"); + return ENOMEM; + } + + return EOK; +} + +static errno_t +cache_req_set_domain(struct cache_req *cr, + struct sss_domain_info *domain) +{ + errno_t ret; + + CACHE_REQ_DEBUG(SSSDBG_TRACE_FUNC, cr, "Using domain [%s]\n", domain->name); + + ret = cache_req_prepare_domain_data(cr, domain); + if (ret != EOK) { + return ret; + } + + ret = cache_req_create_debug_name(cr, domain); + if (ret != EOK) { + return ret; + } + + cr->domain = domain; + + return EOK; +} + +static void cache_req_global_ncache_add(struct cache_req *cr) +{ + errno_t ret; + + if (cr->plugin->global_ncache_add_fn == NULL) { + CACHE_REQ_DEBUG(SSSDBG_TRACE_INTERNAL, cr, + "This request type does not support " + "global negative cache\n"); + return; + } + + CACHE_REQ_DEBUG(SSSDBG_TRACE_FUNC, cr, "Adding [%s] to global " + "negative cache\n", cr->debugobj); + + ret = cr->plugin->global_ncache_add_fn(cr->ncache, cr->data); + if (ret != EOK) { + CACHE_REQ_DEBUG(SSSDBG_MINOR_FAILURE, cr, + "Cannot set negative cache for [%s] [%d]: %s\n", + cr->debugobj, ret, sss_strerror(ret)); + /* not fatal */ + } + + return; +} + +static bool cache_req_check_acct_domain_lookup_type(struct cache_req *cr, + struct sss_domain_info *dom) +{ + struct sss_domain_info *head; + int nret; + + head = get_domains_head(dom); + if (head == NULL) { + return false; + } + + nret = sss_ncache_check_domain_locate_type(cr->rctx->ncache, + head, + cr->plugin->name); + if (nret == ENOENT) { + return true; + } + return false; +} + +static errno_t cache_req_set_acct_domain_lookup_type(struct cache_req *cr, + struct sss_domain_info *dom) +{ + struct sss_domain_info *head; + + head = get_domains_head(dom); + if (head == NULL) { + return EINVAL; + } + + return sss_ncache_set_domain_locate_type(cr->rctx->ncache, + head, + cr->plugin->name); +} + +static void cache_req_domain_set_locate_flag(struct cache_req_domain *domains, + struct cache_req *cr) +{ + struct cache_req_domain *crd_iter; + + DLIST_FOR_EACH(crd_iter, domains) { + if (cache_req_check_acct_domain_lookup_type(cr, crd_iter->domain)) { + crd_iter->locate_domain = true; + } + } +} + +static bool +cache_req_assume_upn(struct cache_req *cr) +{ + errno_t ret; + + if (cr->plugin->allow_switch_to_upn == false + || cr->data->name.input == NULL + || strchr(cr->data->name.input, '@') == NULL) { + return false; + } + + ret = cache_req_set_plugin(cr, cr->plugin->upn_equivalent); + if (ret != EOK) { + return false; + } + + ret = cache_req_set_name(cr, cr->data->name.input); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "cache_req_set_name() failed\n"); + return false; + } + + CACHE_REQ_DEBUG(SSSDBG_TRACE_FUNC, cr, "Assuming UPN [%s]\n", + cr->data->name.input); + + return true; +} + +struct cache_req_locate_dom_state { + /* input data */ + struct tevent_context *ev; + struct cache_req *cr; + struct cache_req_domain *req_domains; + + /* Return values in case the first cache lookup succeeds */ + struct ldb_result *result; + bool dp_success; +}; + +static void cache_req_locate_dom_cache_done(struct tevent_req *subreq); +static void cache_req_locate_dom_done(struct tevent_req *subreq); +static void cache_req_locate_dom_mark_neg_all( + struct cache_req_locate_dom_state *state); +static void cache_req_locate_dom_mark_neg_domains( + struct cache_req_locate_dom_state *state, + const char *found_domain_name); + +static struct tevent_req *cache_req_locate_dom_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct cache_req *cr, + struct cache_req_domain *req_domains) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct cache_req_locate_dom_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct cache_req_locate_dom_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + state->ev = ev; + state->cr = cr; + state->req_domains = req_domains; + + /* It is wasteful to run the domain locator request if the results are + * present in the cache, because the domain locator always contacts + * the DP. Therefore, first run a cache-only search and only if the + * requested data is not available, run the locator + * + * FIXME - this could be optimized further if we are running the + * second iteration with cache_first, then we don't need to search + * again + */ + subreq = cache_req_search_send(state, + state->ev, + state->cr, + false, + true); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + tevent_req_set_callback(subreq, cache_req_locate_dom_cache_done, req); + + return req; + +immediately: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void cache_req_locate_dom_cache_done(struct tevent_req *subreq) +{ + struct cache_req_locate_dom_state *state = NULL; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct cache_req_locate_dom_state); + + ret = cache_req_search_recv(state, subreq, &state->result, &state->dp_success); + talloc_zfree(subreq); + + switch (ret) { + case EOK: + /* Just finish the request and let the caller handle the result */ + DEBUG(SSSDBG_TRACE_INTERNAL, "Result found in the cache\n"); + tevent_req_done(req); + return; + case ERR_ID_OUTSIDE_RANGE: + case ENOENT: + /* Not cached and locator was requested, run the locator + * DP request plugin + */ + subreq = cache_req_locate_domain_send(state, + state->ev, + state->cr); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, cache_req_locate_dom_done, req); + return; + default: + DEBUG(SSSDBG_OP_FAILURE, + "cache_req_search_recv returned [%d]: %s\n", ret, sss_strerror(ret)); + break; + } + + tevent_req_error(req, ret); + return; +} + +static void cache_req_locate_dom_done(struct tevent_req *subreq) +{ + struct cache_req_locate_dom_state *state; + struct tevent_req *req; + errno_t ret; + char *found_domain_name; + int nret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct cache_req_locate_dom_state); + + ret = cache_req_locate_domain_recv(state, subreq, &found_domain_name); + talloc_zfree(subreq); + switch (ret) { + case ERR_GET_ACCT_DOM_NOT_SUPPORTED: + nret = cache_req_set_acct_domain_lookup_type(state->cr, + state->cr->domain); + if (nret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to disable domain locating functionality for %s\n", + state->cr->plugin->name); + } + DEBUG(SSSDBG_CONF_SETTINGS, + "Disabled domain locating functionality for %s\n", + state->cr->plugin->name); + break; + case ERR_NOT_FOUND: + cache_req_locate_dom_mark_neg_all(state); + break; + case EOK: + cache_req_locate_dom_mark_neg_domains(state, found_domain_name); + break; + default: + /* We explicitly ignore errors here */ + break; + } + + tevent_req_done(req); + return; +} + +static void cache_req_locate_dom_mark_neg_all( + struct cache_req_locate_dom_state *state) +{ + struct cache_req_domain *iter; + + DLIST_FOR_EACH(iter, state->req_domains) { + if (get_domains_head(state->cr->domain) != get_domains_head(iter->domain)) { + /* Only add to negative cache for domains from the same "main" + * domain" */ + continue; + } + cache_req_search_ncache_add_to_domain(state->cr, iter->domain); + } +} + +static void cache_req_locate_dom_mark_neg_domains( + struct cache_req_locate_dom_state *state, + const char *found_domain_name) +{ + struct sss_domain_info *found_domain; + struct cache_req_domain *iter; + + found_domain = find_domain_by_name(get_domains_head(state->cr->domain), + found_domain_name, + true); + if (found_domain == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot find domain %s\n", found_domain_name); + return; + } + + /* Set negcache in all subdomains of the one being examined + * except the found one */ + DLIST_FOR_EACH(iter, state->req_domains) { + if (strcasecmp(found_domain_name, + iter->domain->name) == 0) { + continue; + } + + if (get_domains_head(found_domain) != get_domains_head(iter->domain)) { + /* Don't set negative cache for domains outside the main + * domain/subdomain tree b/c the locator request is not + * authoritative for them + */ + continue; + } + cache_req_search_ncache_add_to_domain(state->cr, iter->domain); + } +} + +static errno_t cache_req_locate_dom_cache_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ldb_result **_result, + bool *_dp_success) +{ + struct cache_req_locate_dom_state *state; + + state = tevent_req_data(req, struct cache_req_locate_dom_state); + + if (_dp_success != NULL) { + *_dp_success = state->dp_success; + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (_result != NULL) { + *_result = talloc_steal(mem_ctx, state->result); + } + + return EOK; +} + +struct cache_req_search_domains_state { + /* input data */ + struct tevent_context *ev; + struct cache_req *cr; + + /* work data */ + struct cache_req_domain *cr_domain; + struct cache_req_domain *req_domains; + struct sss_domain_info *selected_domain; + struct cache_req_result **results; + size_t num_results; + bool check_next; + bool dp_success; + bool first_iteration; +}; + +static errno_t cache_req_search_domains_next(struct tevent_req *req); +static errno_t cache_req_handle_result(struct tevent_req *req, + struct ldb_result *result); + +static void cache_req_search_domains_locate_done(struct tevent_req *subreq); + +static void cache_req_search_domains_done(struct tevent_req *subreq); + +static bool +cache_req_dp_contacted(struct cache_req_search_domains_state *state) +{ + switch (state->cr->cache_behavior) { + case CACHE_REQ_CACHE_FIRST: + if (state->first_iteration) { + /* This is the first iteration so provider was bypassed. */ + return false; + } + + /* This is the second iteration so the provider was contacted. */ + return true; + case CACHE_REQ_BYPASS_PROVIDER: + return false; + default: + /* Other schemas talks to provider immediately. */ + return true; + } +} + +struct tevent_req * +cache_req_search_domains_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct cache_req *cr, + struct cache_req_domain *cr_domain, + bool check_next, + bool first_iteration) +{ + struct tevent_req *req; + struct cache_req_search_domains_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct cache_req_search_domains_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->cr = cr; + + state->cr_domain = cr_domain; + state->req_domains = cr_domain; + state->check_next = check_next; + state->dp_success = true; + state->first_iteration = first_iteration; + + if (cr->plugin->dp_get_domain_send_fn != NULL + && ((state->check_next && cr_domain->next != NULL) + || ((state->cr->cache_behavior == CACHE_REQ_CACHE_FIRST) + && !first_iteration))) { + /* If the request is not qualified with a domain name AND + * there are multiple domains to search OR if this is the second + * pass during the "check-cache-first" schema, it makes sense + * to try to run the domain-locator plugin + */ + cache_req_domain_set_locate_flag(cr_domain, cr); + } + + ret = cache_req_search_domains_next(req); + if (ret == EAGAIN) { + return req; + } + + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + + tevent_req_post(req, ev); + return req; +} + +static errno_t cache_req_search_domains_next(struct tevent_req *req) +{ + struct cache_req_search_domains_state *state; + struct tevent_req *subreq; + struct cache_req *cr; + struct sss_domain_info *domain; + uint32_t next_domain_flag; + bool is_domain_valid; + bool allow_no_fqn; + errno_t ret; + + state = tevent_req_data(req, struct cache_req_search_domains_state); + cr = state->cr; + + next_domain_flag = cr->plugin->get_next_domain_flags; + allow_no_fqn = cr->plugin->allow_missing_fqn; + + while (state->cr_domain != NULL) { + domain = state->cr_domain->domain; + + if (domain == NULL) { + break; + } + + /* As the cr_domain list is a flatten version of the domains + * list, we have to ensure to only go through the subdomains in + * case it's specified in the plugin to do so. + */ + if (next_domain_flag == 0 && IS_SUBDOMAIN(domain)) { + state->cr_domain = state->cr_domain->next; + continue; + } + + /* Check if this domain is valid for this request. */ + is_domain_valid = cache_req_validate_domain(cr, domain); + if (!is_domain_valid) { + state->cr_domain = state->cr_domain->next; + continue; + } + + /* If not specified otherwise, we skip domains that require fully + * qualified names on domain less search. We do not descend into + * subdomains here since those are implicitly qualified. + */ + if (state->check_next && !allow_no_fqn && state->cr_domain->fqnames) { + state->cr_domain = state->cr_domain->next; + continue; + } + + state->selected_domain = domain; + + ret = cache_req_set_domain(cr, domain); + if (ret != EOK) { + return ret; + } + + if (state->cr_domain->locate_domain) { + subreq = cache_req_locate_dom_send(state, + state->ev, + cr, + state->req_domains); + if (subreq == NULL) { + return ENOMEM; + } + tevent_req_set_callback(subreq, cache_req_search_domains_locate_done, req); + return EAGAIN; + } + + subreq = cache_req_search_send(state, state->ev, cr, + state->first_iteration, + false); + if (subreq == NULL) { + return ENOMEM; + } + tevent_req_set_callback(subreq, cache_req_search_domains_done, req); + + /* we will continue with the following domain the next time */ + if (state->check_next) { + state->cr_domain = state->cr_domain->next; + } + + return EAGAIN; + } + + /* If we've got some result from previous searches we want to return + * EOK here so the whole cache request is successfully finished. */ + if (state->num_results > 0) { + return EOK; + } + + /* We have searched all available domains and no result was found. + * + * If the plug-in uses a negative cache which is shared among all domains + * (e.g. unique identifiers such as user or group id or sid), we add it + * here and return object not found error. + * + * However, we can only set the negative cache if all data provider + * requests succeeded because only then we can be sure that it does + * not exist- + */ + if (cache_req_dp_contacted(state) && state->dp_success) { + cache_req_global_ncache_add(cr); + } + + return ENOENT; +} + +static void cache_req_search_domains_locate_done(struct tevent_req *subreq) +{ + struct cache_req_search_domains_state *state; + struct ldb_result *result = NULL; + struct tevent_req *req; + bool dp_success; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct cache_req_search_domains_state); + + ret = cache_req_locate_dom_cache_recv(state, subreq, &result, &dp_success); + talloc_zfree(subreq); + + /* Remember if any DP request fails, but here it shouldn't matter + * as the only DP request that should realistically happen is midpoint + * refresh */ + state->dp_success = !dp_success ? false : state->dp_success; + + /* Don't locate the domain again */ + state->cr_domain->locate_domain = false; + + switch (ret) { + case EOK: + if (result != NULL) { + /* Handle result as normally */ + ret = cache_req_handle_result(req, result); + if (ret != EAGAIN) { + goto done; + } + } + break; + default: + /* Some serious error has happened. Finish. */ + goto done; + } + + /* This is a domain less search, continue with the next domain. */ + ret = cache_req_search_domains_next(req); + +done: + switch (ret) { + case EOK: + tevent_req_done(req); + break; + case EAGAIN: + break; + default: + tevent_req_error(req, ret); + break; + } + return; +} + +static errno_t cache_req_handle_result(struct tevent_req *req, + struct ldb_result *result) +{ + struct cache_req_search_domains_state *state; + errno_t ret; + + state = tevent_req_data(req, struct cache_req_search_domains_state); + + /* We got some data from this search. Save it. */ + ret = cache_req_create_and_add_result(state, + state->cr, + state->selected_domain, + result, + state->cr->data->name.lookup, + &state->results, + &state->num_results); + if (ret != EOK) { + /* We were unable to save data. */ + return ret; + } + + if (!state->check_next || !state->cr->plugin->search_all_domains) { + /* We are not interested in more results. */ + return EOK; + } + + return EAGAIN; +} + +static void cache_req_search_domains_done(struct tevent_req *subreq) +{ + struct cache_req_search_domains_state *state; + struct ldb_result *result; + struct tevent_req *req; + bool dp_success; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct cache_req_search_domains_state); + + ret = cache_req_search_recv(state, subreq, &result, &dp_success); + talloc_zfree(subreq); + + /* Remember if any DP request fails, if DP was contacted. */ + if (cache_req_dp_contacted(state)) { + state->dp_success = !dp_success ? false : state->dp_success; + } + + switch (ret) { + case EOK: + ret = cache_req_handle_result(req, result); + if (ret != EAGAIN) { + goto done; + } + break; + case ERR_ID_OUTSIDE_RANGE: + case ENOENT: + if (state->check_next == false) { + if (cache_req_dp_contacted(state) + && !state->dp_success + && state->cr->data->propogate_offline_status) { + /* Not found and data provider request failed so we were + * unable to fetch the data. */ + ret = ERR_OFFLINE; + goto done; + } + + /* Not found. */ + ret = ENOENT; + goto done; + } + + /* Continue with next domain. */ + break; + default: + /* Some serious error has happened. Finish. */ + goto done; + } + + /* This is a domain less search, continue with the next domain. */ + ret = cache_req_search_domains_next(req); + +done: + if (ret == ENOENT && state->results != NULL) { + /* We have at least one result. */ + ret = EOK; + } + + switch (ret) { + case EOK: + tevent_req_done(req); + break; + case EAGAIN: + break; + default: + if (cache_req_dp_contacted(state) + && ret == ENOENT + && !state->dp_success + && state->cr->data->propogate_offline_status) { + /* Not found and data provider request failed so we were + * unable to fetch the data. */ + ret = ERR_OFFLINE; + } + tevent_req_error(req, ret); + break; + } + + return; +} + +static errno_t +cache_req_search_domains_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct cache_req_result ***_results, + size_t *_num_results) +{ + struct cache_req_search_domains_state *state; + + state = tevent_req_data(req, struct cache_req_search_domains_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (_results != NULL) { + *_results = talloc_steal(mem_ctx, state->results); + } + if (_num_results != NULL) { + *_num_results = state->num_results; + } + + return EOK; +} + +struct cache_req_state { + /* input data */ + struct tevent_context *ev; + struct cache_req *cr; + const char *domain_name; + + /* work data */ + struct cache_req_domain *cr_domains; + struct cache_req_result **results; + size_t num_results; + bool first_iteration; +}; + +static errno_t cache_req_process_input(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct cache_req *cr, + const char *domain); + +static errno_t cache_req_update_domains(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct cache_req *cr, + const char *domain); + +static void cache_req_domains_updated(struct tevent_req *subreq); + +static void cache_req_input_parsed(struct tevent_req *subreq); + +static errno_t cache_req_select_domains(struct tevent_req *req, + const char *domain_name, + char **requested_domains); + +static errno_t +cache_req_search_domains(struct tevent_req *req, + struct cache_req_domain *oredered_domain, + bool check_next); + +static void cache_req_process_result(struct tevent_req *subreq); + +static void cache_req_done(struct tevent_req *subreq); + +struct tevent_req *cache_req_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int midpoint, + enum cache_req_dom_type req_dom_type, + const char *domain, + struct cache_req_data *data) +{ + struct cache_req_state *state; + struct cache_req_result *result; + struct cache_req *cr; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct cache_req_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->cr = cr = cache_req_create(state, rctx, data, + ncache, midpoint, req_dom_type); + if (state->cr == NULL) { + ret = ENOMEM; + goto done; + } + state->first_iteration = true; + + SSS_REQ_TRACE_CID_CR(SSSDBG_TRACE_FUNC, cr, "New request [CID #%lu] '%s'\n", + sss_chain_id_get(), cr->reqname); + + ret = cache_req_is_well_known_object(state, cr, &result); + if (ret == EOK) { + ret = cache_req_add_result(state, result, &state->results, + &state->num_results); + goto done; + } else if (ret != ENOENT) { + goto done; + } + + state->domain_name = domain; + ret = cache_req_process_input(state, req, cr, domain); + if (ret != EOK) { + goto done; + } + + ret = cache_req_select_domains(req, state->domain_name, + cr->data->requested_domains); + +done: + if (ret == EOK) { + tevent_req_done(req); + tevent_req_post(req, ev); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static errno_t cache_req_process_input(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct cache_req *cr, + const char *domain) +{ + struct tevent_req *subreq; + const char *default_domain; + errno_t ret; + + if (cr->data->name.input == NULL) { + /* Call cache_req_update_domains() in order to get a up to date list + * of domains and subdomains, if needed. Otherwise just return EOK as + * the input was not a name, thus there's no need to process it + * further. */ + return cache_req_update_domains(mem_ctx, req, cr, domain); + } + + if (cr->plugin->parse_name == false) { + /* Call cache_req_update_domains() in order to get a up to date list + * of domains and subdomains, if needed. Otherwise, just use the input + * name as it is. */ + ret = cache_req_update_domains(mem_ctx, req, cr, domain); + if (ret != EOK) { + return ret; + } + + return cache_req_set_name(cr, cr->data->name.input); + } + + default_domain = NULL; + if (!cr->plugin->ignore_default_domain) { + default_domain = cr->rctx->default_domain; + } + + /* Parse name since it may contain a domain name. */ + CACHE_REQ_DEBUG(SSSDBG_TRACE_FUNC, cr, + "Parsing input name [%s]\n", cr->data->name.input); + + subreq = sss_parse_inp_send(mem_ctx, cr->rctx, default_domain, + cr->data->name.input); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_parse_inp_send() failed\n"); + return ENOMEM; + } + + tevent_req_set_callback(subreq, cache_req_input_parsed, req); + + return EAGAIN; +} + +static errno_t cache_req_update_domains(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct cache_req *cr, + const char *domain) +{ + struct tevent_req *subreq; + + if (cr->rctx->get_domains_last_call.tv_sec != 0) { + return EOK; + } + + subreq = sss_dp_get_domains_send(mem_ctx, cr->rctx, false, domain); + if (subreq == NULL) { + return ENOMEM; + } + + tevent_req_set_callback(subreq, cache_req_domains_updated, req); + return EAGAIN; +} + +static void cache_req_domains_updated(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct cache_req_state *state; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct cache_req_state); + + ret = sss_dp_get_domains_recv(subreq); + talloc_free(subreq); + if (ret != EOK) { + goto done; + } + + if (state->cr->data->name.input == NULL) { + /* Input was not name, there is no need to process it further. */ + goto immediately; + } + + if (state->cr->plugin->parse_name == false || state->domain_name != NULL) { + /* We do not want to parse the name. */ + ret = cache_req_set_name(state->cr, state->cr->data->name.input); + if (ret != EOK) { + goto done; + } + } + +immediately: + ret = cache_req_select_domains(req, state->domain_name, + state->cr->data->requested_domains); + +done: + if (ret != EOK && ret != EAGAIN) { + tevent_req_error(req, ret); + return; + } +} + +static void cache_req_input_parsed(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct cache_req_state *state; + char *name; + char *domain = NULL; + bool maybe_upn; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct cache_req_state); + + ret = sss_parse_inp_recv(subreq, state, &name, &domain); + + if (state->domain_name != NULL && domain != NULL + && strcmp(state->domain_name, domain) != 0){ + DEBUG(SSSDBG_CRIT_FAILURE, + "Mismatch between input domain name [%s] and parsed domain name [%s]\n", + state->domain_name, domain); + tevent_req_error(req, ERR_INPUT_PARSE); + return; + } + + switch (ret) { + case EOK: + ret = cache_req_set_name(state->cr, name); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + break; + case ERR_DOMAIN_NOT_FOUND: + maybe_upn = cache_req_assume_upn(state->cr); + if (!maybe_upn) { + tevent_req_error(req, ret); + return; + } + + domain = NULL; + break; + default: + tevent_req_error(req, ret); + return; + } + + if (state->domain_name == NULL) { + state->domain_name = domain; + } + ret = cache_req_select_domains(req, state->domain_name, + state->cr->data->requested_domains); + if (ret != EAGAIN) { + tevent_req_error(req, ret); + return; + } +} + +static errno_t cache_req_select_domains(struct tevent_req *req, + const char *domain_name, + char **requested_domains) +{ + struct cache_req_state *state = NULL; + struct cache_req_domain *cr_domain; + bool check_next; + errno_t ret; + + state = tevent_req_data(req, struct cache_req_state); + + if (state->cr->cache_behavior != CACHE_REQ_CACHE_FIRST) { + + if (!state->first_iteration) { + /* We're done here. */ + return EOK; + } + } + + ret = cache_req_domain_copy_cr_domains(state, + state->cr->rctx->cr_domains, + requested_domains, + &state->cr_domains); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "cache_req_copy_cr_domains() failed\n"); + return EINVAL; + } + + if (domain_name != NULL) { + CACHE_REQ_DEBUG(SSSDBG_TRACE_FUNC, state->cr, + "Performing a single domain search\n"); + + cr_domain = cache_req_domain_get_domain_by_name( + state->cr_domains, domain_name); + if (cr_domain == NULL) { + return ERR_DOMAIN_NOT_FOUND; + } + check_next = false; + } else { + CACHE_REQ_DEBUG(SSSDBG_TRACE_FUNC, state->cr, + "Performing a multi-domain search\n"); + + cr_domain = state->cr_domains; + check_next = true; + } + + return cache_req_search_domains(req, cr_domain, check_next); +} + +static errno_t +cache_req_search_domains(struct tevent_req *req, + struct cache_req_domain *cr_domain, + bool check_next) +{ + struct tevent_req *subreq; + struct cache_req_state *state = NULL; + const char *cache_action; + const char *provider_action; + + state = tevent_req_data(req, struct cache_req_state); + + switch (state->cr->cache_behavior) { + case CACHE_REQ_CACHE_FIRST: + cache_action = (state->first_iteration) ? "check" : "bypass"; + provider_action = (state->first_iteration) ? "bypass" : "check"; + break; + case CACHE_REQ_BYPASS_CACHE: + cache_action = "bypass"; + provider_action = "check"; + break; + case CACHE_REQ_BYPASS_PROVIDER: + cache_action = "check"; + provider_action = "bypass"; + break; + default: + cache_action = "check"; + provider_action = "check"; + break; + } + CACHE_REQ_DEBUG(SSSDBG_TRACE_FUNC, state->cr, + "Search will %s the cache and %s the data provider\n", + cache_action, provider_action); + + subreq = cache_req_search_domains_send(state, state->ev, state->cr, + cr_domain, check_next, + state->first_iteration); + if (subreq == NULL) { + return ENOMEM; + } + + tevent_req_set_callback(subreq, cache_req_process_result, req); + return EAGAIN; +} + +static void cache_req_process_result(struct tevent_req *subreq) +{ + struct cache_req_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct cache_req_state); + + ret = cache_req_search_domains_recv(state, subreq, + &state->results, &state->num_results); + talloc_zfree(subreq); + + if (ret == ENOENT && state->first_iteration) { + /* Try again different search schema. */ + state->first_iteration = false; + ret = cache_req_select_domains(req, state->domain_name, + state->cr->data->requested_domains); + if (ret == EOK) { + /* We're done searching and we have found nothing. */ + ret = ENOENT; + } + } + + /* Have have tried all domains and found nothing. Let's try UPN search. */ + if (ret == ENOENT) { + if (state->domain_name != NULL) { + /* Lookup domain was specified as input. Since we haven't + * found anything yet we may want to try UPN search with + * some plug-ins. */ + + if (cache_req_assume_upn(state->cr)) { + /* Try UPN now. */ + state->first_iteration = true; + ret = cache_req_select_domains(req, NULL, + state->cr->data->requested_domains); + } + } + } + + /* Overlay each result with session recording flag */ + if (ret == EOK) { + subreq = cache_req_sr_overlay_send(state, state->ev, state->cr, + state->results, + state->num_results); + if (subreq == NULL) { + CACHE_REQ_DEBUG(SSSDBG_CRIT_FAILURE, state->cr, + "Failed creating a session recording " + "overlay request\n"); + ret = ENOMEM; + } else { + tevent_req_set_callback(subreq, cache_req_done, req); + ret = EAGAIN; + } + } + + switch (ret) { + case EAGAIN: + break; + case ENOENT: + CACHE_REQ_DEBUG(SSSDBG_TRACE_FUNC, state->cr, "Finished: Not found\n"); + tevent_req_error(req, ret); + break; + default: + CACHE_REQ_DEBUG(SSSDBG_TRACE_FUNC, state->cr, + "Finished: Error %d: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + break; + } + + return; +} + +static void cache_req_done(struct tevent_req *subreq) +{ + struct cache_req_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct cache_req_state); + ret = cache_req_sr_overlay_recv(subreq); + talloc_zfree(subreq); + + switch (ret) { + case EOK: + CACHE_REQ_DEBUG(SSSDBG_TRACE_FUNC, state->cr, "Finished: Success\n"); + tevent_req_done(req); + break; + default: + CACHE_REQ_DEBUG(SSSDBG_TRACE_FUNC, state->cr, + "Finished: Error %d: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + break; + } +} + +uint32_t cache_req_get_reqid(struct tevent_req *req) +{ + const struct cache_req_state *state; + + state = tevent_req_data(req, struct cache_req_state); + + if (state && state->cr) { + return state->cr->reqid; + } + + return 0; +} + +errno_t cache_req_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct cache_req_result ***_results) +{ + struct cache_req_state *state; + + state = tevent_req_data(req, struct cache_req_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (_results != NULL) { + *_results = talloc_steal(mem_ctx, state->results); + } + + return EOK; +} + +errno_t cache_req_single_domain_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct cache_req_result **_result) +{ + struct cache_req_state *state; + + state = tevent_req_data(req, struct cache_req_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (_result != NULL) { + *_result = talloc_steal(mem_ctx, state->results[0]); + } + + return EOK; +} + +struct tevent_req * +cache_req_steal_data_and_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + enum cache_req_dom_type req_dom_type, + const char *domain, + struct cache_req_data *data) +{ + struct tevent_req *req; + + req = cache_req_send(mem_ctx, ev, rctx, ncache, + cache_refresh_percent, + req_dom_type, domain, data); + if (req == NULL) { + talloc_zfree(data); + return NULL; + } + + talloc_steal(req, data); + + return req; +} diff --git a/src/responder/common/cache_req/cache_req.h b/src/responder/common/cache_req/cache_req.h new file mode 100644 index 0000000..a0c6879 --- /dev/null +++ b/src/responder/common/cache_req/cache_req.h @@ -0,0 +1,521 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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/>. +*/ + +#ifndef _CACHE_REQ_H_ +#define _CACHE_REQ_H_ + +#include "util/util.h" +#include "confdb/confdb.h" +#include "responder/common/negcache.h" + +enum cache_req_type { + CACHE_REQ_USER_BY_NAME, + CACHE_REQ_USER_BY_UPN, + CACHE_REQ_USER_BY_ID, + CACHE_REQ_USER_BY_CERT, + CACHE_REQ_USER_BY_FILTER, + + CACHE_REQ_GROUP_BY_NAME, + CACHE_REQ_GROUP_BY_ID, + CACHE_REQ_GROUP_BY_FILTER, + + CACHE_REQ_INITGROUPS, + CACHE_REQ_INITGROUPS_BY_UPN, + +#ifdef BUILD_SUBID + CACHE_REQ_SUBID_RANGES_BY_NAME, +#endif + + CACHE_REQ_OBJECT_BY_SID, + CACHE_REQ_OBJECT_BY_NAME, + CACHE_REQ_OBJECT_BY_ID, + + CACHE_REQ_ENUM_USERS, + CACHE_REQ_ENUM_GROUPS, + CACHE_REQ_ENUM_SVC, + CACHE_REQ_ENUM_HOST, + CACHE_REQ_ENUM_IP_NETWORK, + + CACHE_REQ_SVC_BY_NAME, + CACHE_REQ_SVC_BY_PORT, + + CACHE_REQ_NETGROUP_BY_NAME, + + CACHE_REQ_SSH_HOST_ID_BY_NAME, + + CACHE_REQ_AUTOFS_MAP_ENTRIES, + CACHE_REQ_AUTOFS_MAP_BY_NAME, + CACHE_REQ_AUTOFS_ENTRY_BY_NAME, + + CACHE_REQ_IP_HOST_BY_NAME, + CACHE_REQ_IP_HOST_BY_ADDR, + CACHE_REQ_IP_NETWORK_BY_NAME, + CACHE_REQ_IP_NETWORK_BY_ADDR, + + CACHE_REQ_SENTINEL +}; + +/* Whether to limit the request type to a certain domain type + * (POSIX/non-POSIX) + */ +enum cache_req_dom_type { + /* Only look up data in POSIX domains */ + CACHE_REQ_POSIX_DOM, + /* Only look up data in application domains */ + CACHE_REQ_APPLICATION_DOM, + /* Look up data in any domain type */ + CACHE_REQ_ANY_DOM +}; + +/* Controls behavior about how to use cached information during + * a lookup, this is to fine tune some behaviors for specific + * situations + */ +enum cache_req_behavior { + CACHE_REQ_NORMAL, + CACHE_REQ_CACHE_FIRST, + CACHE_REQ_BYPASS_CACHE, + CACHE_REQ_BYPASS_PROVIDER, +}; + +/* Input data. */ + +struct cache_req_data; + +struct cache_req_data * +cache_req_data_attr(TALLOC_CTX *mem_ctx, + enum cache_req_type type, + const char *attr, + const char *filter); + +struct cache_req_data * +cache_req_data_name(TALLOC_CTX *mem_ctx, + enum cache_req_type type, + const char *name); + +struct cache_req_data * +cache_req_data_name_attrs(TALLOC_CTX *mem_ctx, + enum cache_req_type type, + const char *name, + const char **attrs); + +struct cache_req_data * +cache_req_data_id(TALLOC_CTX *mem_ctx, + enum cache_req_type type, + uint32_t id); + +struct cache_req_data * +cache_req_data_id_attrs(TALLOC_CTX *mem_ctx, + enum cache_req_type type, + uint32_t id, + const char **attrs); + +struct cache_req_data * +cache_req_data_cert(TALLOC_CTX *mem_ctx, + enum cache_req_type type, + const char *cert); + +struct cache_req_data * +cache_req_data_sid(TALLOC_CTX *mem_ctx, + enum cache_req_type type, + const char *sid, + const char **attrs); + +struct cache_req_data * +cache_req_data_addr(TALLOC_CTX *mem_ctx, + enum cache_req_type type, + uint32_t af, + uint32_t addrlen, + uint8_t *addr); + +struct cache_req_data * +cache_req_data_enum(TALLOC_CTX *mem_ctx, + enum cache_req_type type); + +struct cache_req_data * +cache_req_data_svc(TALLOC_CTX *mem_ctx, + enum cache_req_type type, + const char *name, + const char *protocol, + uint16_t port); + +struct cache_req_data * +cache_req_data_ssh_host_id(TALLOC_CTX *mem_ctx, + enum cache_req_type type, + const char *name, + const char *alias, + const char **attrs); + +struct cache_req_data * +cache_req_data_autofs_entry(TALLOC_CTX *mem_ctx, + enum cache_req_type type, + const char *mapname, + const char *entryname); + +void +cache_req_data_set_bypass_cache(struct cache_req_data *data, + bool bypass_cache); + +void +cache_req_data_set_bypass_dp(struct cache_req_data *data, + bool bypass_dp); + +void +cache_req_data_set_requested_domains(struct cache_req_data *data, + char **requested_domains); + +void +cache_req_data_set_propogate_offline_status(struct cache_req_data *data, + bool propogate_offline_status); + +void +cache_req_data_set_hybrid_lookup(struct cache_req_data *data, + bool hybrid_lookup); + +enum cache_req_type +cache_req_data_get_type(struct cache_req_data *data); + +/* Output data. */ + +struct cache_req_result { + /** + * SSSD domain where the result was obtained. + */ + struct sss_domain_info *domain; + + /** + * Result from ldb lookup. + */ + struct ldb_result *ldb_result; + + /** + * Shortcuts into ldb_result. This shortens the code a little since + * callers usually don't don't need to work with ldb_result directly. + */ + unsigned int count; + struct ldb_message **msgs; + + /** + * If name was used as a lookup parameter, @lookup_name contains name + * normalized to @domain rules. + */ + const char *lookup_name; + + /** + * If true the result contain attributes of a well known object. + * Since this result is manually created it may not contain all + * requested attributes, depending on the plug-in. + */ + bool well_known_object; + + /* If this is a well known object, it may not be part of any particular + * SSSD domain, but still may be associated with a well known domain + * name such as "BUILTIN", or "LOCAL AUTHORITY". + */ + const char *well_known_domain; +}; + +/** + * Shallow copy of cache request result, limiting the result to a maximum + * numbers of records. + */ +struct cache_req_result * +cache_req_copy_limited_result(TALLOC_CTX *mem_ctx, + struct cache_req_result *result, + uint32_t start, + uint32_t limit); + +/* Generic request. */ + +struct tevent_req *cache_req_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int midpoint, + enum cache_req_dom_type req_dom_type, + const char *domain, + struct cache_req_data *data); + +uint32_t cache_req_get_reqid(struct tevent_req *req); + +errno_t cache_req_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct cache_req_result ***_results); + +errno_t cache_req_single_domain_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct cache_req_result **_result); + +/* Plug-ins. */ + +struct tevent_req * +cache_req_user_by_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + enum cache_req_dom_type req_dom_type, + const char *domain, + const char *name); + +#define cache_req_user_by_name_recv(mem_ctx, req, _result) \ + cache_req_single_domain_recv(mem_ctx, req, _result) + +struct tevent_req * +cache_req_user_by_name_attrs_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + const char *domain, + const char *name, + const char **attrs); + +#define cache_req_user_by_name_attrs_recv(mem_ctx, req, _result) \ + cache_req_single_domain_recv(mem_ctx, req, _result) + +struct tevent_req * +cache_req_user_by_upn_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + enum cache_req_dom_type req_dom_type, + const char *domain, + const char *upn); + +#define cache_req_user_by_upn_recv(mem_ctx, req, _result) \ + cache_req_single_domain_recv(mem_ctx, req, _result); + +struct tevent_req * +cache_req_user_by_id_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + const char *domain, + uid_t uid); + +#define cache_req_user_by_id_recv(mem_ctx, req, _result) \ + cache_req_single_domain_recv(mem_ctx, req, _result); + +struct tevent_req * +cache_req_user_by_cert_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + enum cache_req_dom_type req_dom_type, + const char *domain, + const char *pem_cert); + +#define cache_req_user_by_cert_recv(mem_ctx, req, _result) \ + cache_req_single_domain_recv(mem_ctx, req, _result) + +struct tevent_req * +cache_req_group_by_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + enum cache_req_dom_type req_dom_type, + const char *domain, + const char *name); + +#define cache_req_group_by_name_recv(mem_ctx, req, _result) \ + cache_req_single_domain_recv(mem_ctx, req, _result) + +struct tevent_req * +cache_req_group_by_id_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + const char *domain, + gid_t gid); + +#define cache_req_group_by_id_recv(mem_ctx, req, _result) \ + cache_req_single_domain_recv(mem_ctx, req, _result) + +struct tevent_req * +cache_req_initgr_by_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + enum cache_req_dom_type req_dom_type, + const char *domain, + const char *name); + +#define cache_req_initgr_by_name_recv(mem_ctx, req, _result) \ + cache_req_single_domain_recv(mem_ctx, req, _result) + +struct tevent_req * +cache_req_user_by_filter_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + enum cache_req_dom_type req_dom_type, + const char *domain, + const char *attr, + const char *filter); + +#define cache_req_user_by_filter_recv(mem_ctx, req, _result) \ + cache_req_single_domain_recv(mem_ctx, req, _result) + +struct tevent_req * +cache_req_group_by_filter_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + enum cache_req_dom_type req_dom_type, + const char *domain, + const char *filter); + +#define cache_req_group_by_filter_recv(mem_ctx, req, _result) \ + cache_req_single_domain_recv(mem_ctx, req, _result) + +struct tevent_req * +cache_req_object_by_sid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + const char *domain, + const char *sid, + const char **attrs); + +#define cache_req_object_by_sid_recv(mem_ctx, req, _result) \ + cache_req_single_domain_recv(mem_ctx, req, _result) + +struct tevent_req * +cache_req_object_by_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + const char *domain, + const char *name, + const char **attrs); + +#define cache_req_object_by_name_recv(mem_ctx, req, _result) \ + cache_req_single_domain_recv(mem_ctx, req, _result) + +struct tevent_req * +cache_req_object_by_id_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + const char *domain, + uint32_t id, + const char **attrs); + +#define cache_req_object_by_id_recv(mem_ctx, req, _result) \ + cache_req_single_domain_recv(mem_ctx, req, _result) + +struct tevent_req * +cache_req_svc_by_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + const char *domain, + const char *name, + const char *protocol); + +#define cache_req_svc_by_name_recv(mem_ctx, req, _result) \ + cache_req_single_domain_recv(mem_ctx, req, _result) + +struct tevent_req * +cache_req_svc_by_port_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + const char *domain, + uint16_t port, + const char *protocol); + +#define cache_req_svc_by_port_recv(mem_ctx, req, _result) \ + cache_req_single_domain_recv(mem_ctx, req, _result) + +struct tevent_req * +cache_req_netgroup_by_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + const char *domain, + const char *name); + +#define cache_req_netgroup_by_name_recv(mem_ctx, req, _result) \ + cache_req_single_domain_recv(mem_ctx, req, _result) + +struct tevent_req * +cache_req_ssh_host_id_by_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + const char *domain, + const char *name, + const char *alias, + const char **attrs); + +#define cache_req_ssh_host_id_by_name_recv(mem_ctx, req, _result) \ + cache_req_single_domain_recv(mem_ctx, req, _result) + +struct tevent_req * +cache_req_autofs_map_entries_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + const char *domain, + const char *name); + +#define cache_req_autofs_map_entries_recv(mem_ctx, req, _result) \ + cache_req_single_domain_recv(mem_ctx, req, _result) + +struct tevent_req * +cache_req_autofs_map_by_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + const char *domain, + const char *name); + +#define cache_req_autofs_map_by_name_recv(mem_ctx, req, _result) \ + cache_req_single_domain_recv(mem_ctx, req, _result) + +struct tevent_req * +cache_req_autofs_entry_by_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + const char *domain, + const char *mapname, + const char *entryname); + +#define cache_req_autofs_entry_by_name_recv(mem_ctx, req, _result) \ + cache_req_single_domain_recv(mem_ctx, req, _result) + +#endif /* _CACHE_REQ_H_ */ diff --git a/src/responder/common/cache_req/cache_req_data.c b/src/responder/common/cache_req/cache_req_data.c new file mode 100644 index 0000000..c506de5 --- /dev/null +++ b/src/responder/common/cache_req/cache_req_data.c @@ -0,0 +1,519 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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 <talloc.h> + +#include "db/sysdb.h" +#include "responder/common/cache_req/cache_req_private.h" + +static const char ** +cache_req_data_create_attrs(TALLOC_CTX *mem_ctx, + const char **requested) +{ + static const char *defattrs[] = { SYSDB_DEFAULT_ATTRS, SYSDB_NAME, + OVERRIDE_PREFIX SYSDB_NAME, + SYSDB_DEFAULT_OVERRIDE_NAME }; + static size_t defnum = sizeof(defattrs) / sizeof(defattrs[0]); + const char **attrs; + size_t reqnum; + size_t total; + size_t i; + + for (reqnum = 0; requested[reqnum] != NULL; reqnum++); + + total = defnum + reqnum; + + /* We always want to get default attributes. */ + attrs = talloc_zero_array(mem_ctx, const char *, total + 1); + if (attrs == NULL) { + return NULL; + } + + for (i = 0; i < reqnum; i++) { + attrs[i] = talloc_strdup(attrs, requested[i]); + if (attrs[i] == NULL) { + talloc_free(attrs); + return NULL; + } + } + + for (/* continue */; i < total; i++) { + attrs[i] = talloc_strdup(attrs, defattrs[i - reqnum]); + if (attrs[i] == NULL) { + talloc_free(attrs); + return NULL; + } + } + + return attrs; +} + +static struct cache_req_data * +cache_req_data_create(TALLOC_CTX *mem_ctx, + enum cache_req_type type, + const struct cache_req_data *input) +{ + struct cache_req_data *data; + errno_t ret; + + data = talloc_zero(mem_ctx, struct cache_req_data); + if (data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero() failed\n"); + return NULL; + } + + data->type = type; + data->svc.name = &data->name; + + switch (type) { + case CACHE_REQ_USER_BY_FILTER: + if (input->name.attr == NULL) { + data->name.attr = NULL; + } else { + data->name.attr = talloc_strdup(data, input->name.attr); + if (data->name.attr == NULL) { + ret = ENOMEM; + goto done; + } + } + /* Fallthrough */ + case CACHE_REQ_USER_BY_NAME: + case CACHE_REQ_USER_BY_UPN: + case CACHE_REQ_GROUP_BY_NAME: + case CACHE_REQ_GROUP_BY_FILTER: + case CACHE_REQ_INITGROUPS: + case CACHE_REQ_INITGROUPS_BY_UPN: +#ifdef BUILD_SUBID + case CACHE_REQ_SUBID_RANGES_BY_NAME: +#endif + case CACHE_REQ_NETGROUP_BY_NAME: + case CACHE_REQ_OBJECT_BY_NAME: + case CACHE_REQ_AUTOFS_MAP_ENTRIES: + case CACHE_REQ_AUTOFS_MAP_BY_NAME: + case CACHE_REQ_IP_HOST_BY_NAME: + case CACHE_REQ_IP_NETWORK_BY_NAME: + if (input->name.input == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: name cannot be NULL!\n"); + ret = ERR_INTERNAL; + goto done; + } + + data->name.input = talloc_strdup(data, input->name.input); + if (data->name.input == NULL) { + ret = ENOMEM; + goto done; + } + break; + case CACHE_REQ_IP_HOST_BY_ADDR: + case CACHE_REQ_IP_NETWORK_BY_ADDR: + data->addr.af = input->addr.af; + data->addr.len = input->addr.len; + data->addr.data = talloc_memdup(data, input->addr.data, + input->addr.len); + if (data->addr.data == NULL) { + ret = ENOMEM; + goto done; + } + break; + case CACHE_REQ_USER_BY_CERT: + if (input->cert == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: certificate cannot be NULL!\n"); + ret = ERR_INTERNAL; + goto done; + } + + data->cert = talloc_strdup(data, input->cert); + if (data->cert == NULL) { + ret = ENOMEM; + goto done; + } + break; + case CACHE_REQ_USER_BY_ID: + case CACHE_REQ_GROUP_BY_ID: + case CACHE_REQ_OBJECT_BY_ID: + data->id = input->id; + break; + case CACHE_REQ_OBJECT_BY_SID: + if (input->sid == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: SID cannot be NULL!\n"); + ret = ERR_INTERNAL; + goto done; + } + + data->sid = talloc_strdup(data, input->sid); + if (data->sid == NULL) { + ret = ENOMEM; + goto done; + } + break; + case CACHE_REQ_ENUM_USERS: + case CACHE_REQ_ENUM_GROUPS: + case CACHE_REQ_ENUM_SVC: + case CACHE_REQ_ENUM_HOST: + case CACHE_REQ_ENUM_IP_NETWORK: + break; + case CACHE_REQ_SVC_BY_NAME: + if ((input->svc.name == NULL) || (input->svc.name->input == NULL)) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: name cannot be NULL!\n"); + ret = ERR_INTERNAL; + goto done; + } + + data->svc.name->input = talloc_strdup(data, input->svc.name->input); + if (data->svc.name->input == NULL) { + ret = ENOMEM; + goto done; + } + + if (input->svc.protocol.name == NULL) { + break; + } + + data->svc.protocol.name = talloc_strdup(data, input->svc.protocol.name); + if (data->svc.protocol.name == NULL) { + ret = ENOMEM; + goto done; + } + + break; + case CACHE_REQ_SVC_BY_PORT: + if (input->svc.port == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: port cannot be 0!\n"); + ret = ERR_INTERNAL; + goto done; + } + + data->svc.port = input->svc.port; + + if (input->svc.protocol.name == NULL) { + break; + } + + data->svc.protocol.name = talloc_strdup(data, input->svc.protocol.name); + if (data->svc.protocol.name == NULL) { + ret = ENOMEM; + goto done; + } + + break; + case CACHE_REQ_SSH_HOST_ID_BY_NAME: + if (input->name.input == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: name cannot be NULL!\n"); + ret = ERR_INTERNAL; + goto done; + } + + data->name.input = talloc_strdup(data, input->name.input); + if (data->name.input == NULL) { + ret = ENOMEM; + goto done; + } + + if (input->alias == NULL) { + break; + } + + data->alias = talloc_strdup(data, input->alias); + if (data->alias == NULL) { + ret = ENOMEM; + goto done; + } + break; + case CACHE_REQ_AUTOFS_ENTRY_BY_NAME: + if (input->name.input == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: name cannot be NULL!\n"); + ret = ERR_INTERNAL; + goto done; + } + + data->name.input = talloc_strdup(data, input->name.input); + if (data->name.input == NULL) { + ret = ENOMEM; + goto done; + } + + data->autofs_entry_name = talloc_strdup(data, input->autofs_entry_name); + if (data->autofs_entry_name == NULL) { + ret = ENOMEM; + goto done; + } + break; + case CACHE_REQ_SENTINEL: + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid cache request type!\n"); + ret = ERR_INTERNAL; + goto done; + } + + if (input->attrs != NULL) { + data->attrs = cache_req_data_create_attrs(data, input->attrs); + if (data->attrs == NULL) { + ret = ENOMEM; + goto done; + } + } + + ret = EOK; + +done: + if (ret != EOK) { + talloc_zfree(data); + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create cache_req data " + "[%d]: %s\n", ret, sss_strerror(ret)); + return NULL; + } + + return data; +} + +struct cache_req_data * +cache_req_data_name(TALLOC_CTX *mem_ctx, + enum cache_req_type type, + const char *name) +{ + struct cache_req_data input = {0}; + + input.name.input = name; + + return cache_req_data_create(mem_ctx, type, &input); +} + +struct cache_req_data * +cache_req_data_name_attrs(TALLOC_CTX *mem_ctx, + enum cache_req_type type, + const char *name, + const char **attrs) +{ + struct cache_req_data input = { 0 }; + + input.name.input = name; + input.attrs = attrs; + + return cache_req_data_create(mem_ctx, type, &input); +} + +struct cache_req_data * +cache_req_data_attr(TALLOC_CTX *mem_ctx, + enum cache_req_type type, + const char *attr, + const char *filter) +{ + struct cache_req_data input = {0}; + + input.name.input = filter; + input.name.attr = attr; + + return cache_req_data_create(mem_ctx, type, &input); +} + +struct cache_req_data * +cache_req_data_id(TALLOC_CTX *mem_ctx, + enum cache_req_type type, + uint32_t id) +{ + struct cache_req_data input = {0}; + + input.id = id; + + return cache_req_data_create(mem_ctx, type, &input); +} + +struct cache_req_data * +cache_req_data_id_attrs(TALLOC_CTX *mem_ctx, + enum cache_req_type type, + uint32_t id, + const char **attrs) +{ + struct cache_req_data input = { 0 }; + + input.id = id; + input.attrs = attrs; + + return cache_req_data_create(mem_ctx, type, &input); +} + +struct cache_req_data * +cache_req_data_cert(TALLOC_CTX *mem_ctx, + enum cache_req_type type, + const char *cert) +{ + struct cache_req_data input = {0}; + + input.cert = cert; + + return cache_req_data_create(mem_ctx, type, &input); +} + +struct cache_req_data * +cache_req_data_sid(TALLOC_CTX *mem_ctx, + enum cache_req_type type, + const char *sid, + const char **attrs) +{ + struct cache_req_data input = {0}; + + input.sid = sid; + input.attrs = attrs; + + return cache_req_data_create(mem_ctx, type, &input); +} + +struct cache_req_data * +cache_req_data_enum(TALLOC_CTX *mem_ctx, + enum cache_req_type type) +{ + struct cache_req_data input = { 0 }; + + return cache_req_data_create(mem_ctx, type, &input); +} + +struct cache_req_data * +cache_req_data_svc(TALLOC_CTX *mem_ctx, + enum cache_req_type type, + const char *name, + const char *protocol, + uint16_t port) +{ + struct cache_req_data input = { 0 }; + + input.name.input = name; + input.svc.name = &input.name; + input.svc.protocol.name = protocol; + input.svc.port = port; + + return cache_req_data_create(mem_ctx, type, &input); +} + +struct cache_req_data * +cache_req_data_ssh_host_id(TALLOC_CTX *mem_ctx, + enum cache_req_type type, + const char *name, + const char *alias, + const char **attrs) +{ + struct cache_req_data input = {0}; + + input.name.input = name; + input.alias = alias; + input.attrs = attrs; + + return cache_req_data_create(mem_ctx, type, &input); +} + +struct cache_req_data * +cache_req_data_addr(TALLOC_CTX *mem_ctx, + enum cache_req_type type, + uint32_t af, + uint32_t addrlen, + uint8_t *addr) +{ + struct cache_req_data input = {0}; + + input.addr.af = af; + input.addr.len = addrlen; + input.addr.data = addr; + + return cache_req_data_create(mem_ctx, type, &input); +} + +struct cache_req_data * +cache_req_data_autofs_entry(TALLOC_CTX *mem_ctx, + enum cache_req_type type, + const char *mapname, + const char *entryname) +{ + struct cache_req_data input = {0}; + + input.name.input = mapname; + input.autofs_entry_name = entryname; + + return cache_req_data_create(mem_ctx, type, &input); +} + +void +cache_req_data_set_bypass_cache(struct cache_req_data *data, + bool bypass_cache) +{ + if (data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "cache_req_data should never be NULL\n"); + return; + } + + data->bypass_cache = bypass_cache; +} + +void +cache_req_data_set_bypass_dp(struct cache_req_data *data, + bool bypass_dp) +{ + if (data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "cache_req_data should never be NULL\n"); + return; + } + + data->bypass_dp = bypass_dp; +} + +void +cache_req_data_set_requested_domains(struct cache_req_data *data, + char **requested_domains) +{ + if (data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "cache_req_data should never be NULL\n"); + return; + } + + data->requested_domains = requested_domains; +} + +void +cache_req_data_set_propogate_offline_status(struct cache_req_data *data, + bool propogate_offline_status) +{ + if (data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "cache_req_data should never be NULL\n"); + return; + } + + data->propogate_offline_status = propogate_offline_status; +} + +void +cache_req_data_set_hybrid_lookup(struct cache_req_data *data, + bool hybrid_lookup) +{ + if (data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "cache_req_data should never be NULL\n"); + return; + } + + data->hybrid_lookup = hybrid_lookup; +} + + +enum cache_req_type +cache_req_data_get_type(struct cache_req_data *data) +{ + if (data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "cache_req_data should never be NULL\n"); + return CACHE_REQ_SENTINEL; + } + + return data->type; +} diff --git a/src/responder/common/cache_req/cache_req_domain.c b/src/responder/common/cache_req/cache_req_domain.c new file mode 100644 index 0000000..1f3b690 --- /dev/null +++ b/src/responder/common/cache_req/cache_req_domain.c @@ -0,0 +1,317 @@ +/* + Authors: + Fabiano Fidêncio <fidencio@redhat.com> + + Copyright (C) 2017 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 "responder/common/cache_req/cache_req_domain.h" + +struct cache_req_domain * +cache_req_domain_get_domain_by_name(struct cache_req_domain *domains, + const char *name) +{ + struct cache_req_domain *dom; + struct cache_req_domain *ret = NULL; + + DLIST_FOR_EACH(dom, domains) { + if (sss_domain_get_state(dom->domain) == DOM_DISABLED) { + continue; + } + + if (strcasecmp(dom->domain->name, name) == 0 || + (dom->domain->flat_name != NULL && + strcasecmp(dom->domain->flat_name, name) == 0)) { + ret = dom; + break; + } + } + + if (ret == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Unknown domains [%s].\n", name); + } + + return ret; +} + +errno_t +cache_req_domain_copy_cr_domains(TALLOC_CTX *mem_ctx, + struct cache_req_domain *src, + char **requested_domains, + struct cache_req_domain **_dest) +{ + struct cache_req_domain *cr_domains = NULL; + struct cache_req_domain *cr_domain; + struct cache_req_domain *iter; + errno_t ret; + + if (src == NULL) { + return EINVAL; + } + + DLIST_FOR_EACH(iter, src) { + if (requested_domains != NULL + && !string_in_list(iter->domain->name, requested_domains, + false)) { + continue; + } + + cr_domain = talloc_zero(mem_ctx, struct cache_req_domain); + if (cr_domain == NULL) { + ret = ENOMEM; + goto done; + } + + cr_domain->domain = iter->domain; + cr_domain->fqnames = iter->fqnames; + + DLIST_ADD_END(cr_domains, cr_domain, struct cache_req_domain *); + } + + if (cr_domains == NULL) { + if (requested_domains != NULL) { + DEBUG(SSSDBG_OP_FAILURE, "No requested domains found, " + "please check configuration options for typos.\n"); + } else { + DEBUG(SSSDBG_OP_FAILURE, "Failed to copy domains.\n"); + } + ret = EINVAL; + goto done; + } + + *_dest = cr_domains; + ret = EOK; + +done: + if (ret != EOK) { + cache_req_domain_list_zfree(&cr_domains); + } + + return ret; +} + +void cache_req_domain_list_zfree(struct cache_req_domain **cr_domains) +{ + struct cache_req_domain *p, *q, *r; + + DLIST_FOR_EACH_SAFE(p, q, *cr_domains) { + r = p; + DLIST_REMOVE(*cr_domains, p); + talloc_zfree(r); + } + + *cr_domains = NULL; +} + +static bool +cache_req_domain_use_fqnames(struct sss_domain_info *domain, + bool enforce_non_fqnames) +{ + struct sss_domain_info *head; + + head = get_domains_head(domain); + + /* + * In order to decide whether fully_qualified_names must be used on the + * lookups we have to take into consideration: + * - use_fully_qualified_name value of the head of the domains; + * (head->fqnames) + * - the presence of a domains' resolution order list; + * (non_fqnames_enforced) + * + * The relationship between those two can be described by: + * - head->fqnames: + * - true: in this case doesn't matter whether it's enforced or not, + * fully-qualified-names will _always_ be used + * - false: in this case (which is also the default case), the usage + * depends on it being enforced; + * + * - enforce_non_fqnames: + * - true: in this case, the usage of fully-qualified-names is not + * needed; + * - false: in this case, the usage of fully-qualified-names will be + * done accordingly to what's set for the domain itself. + */ + if (head->fqnames) { + return true; + } else if (enforce_non_fqnames) { + return false; + } else { + return domain->fqnames; + } +} + +static struct cache_req_domain * +cache_req_domain_new_list_from_string_list(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domains, + char **resolution_order) +{ + struct cache_req_domain *cr_domains = NULL; + struct cache_req_domain *cr_domain; + struct sss_domain_info *dom; + char *name; + int flag = SSS_GND_ALL_DOMAINS; + int i; + bool enforce_non_fqnames = false; + bool files_provider = false; + errno_t ret; + + /* Firstly, in case a domains' resolution order is passed ... iterate over + * the list adding its domains to the flatten cache req domains' list */ + if (resolution_order != NULL) { + enforce_non_fqnames = true; + for (i = 0; resolution_order[i] != NULL; i++) { + name = resolution_order[i]; + for (dom = domains; dom; dom = get_next_domain(dom, flag)) { + if (strcasecmp(name, dom->name) != 0) { + continue; + } + + cr_domain = talloc_zero(mem_ctx, struct cache_req_domain); + if (cr_domain == NULL) { + ret = ENOMEM; + goto done; + } + cr_domain->domain = dom; + cr_domain->fqnames = + cache_req_domain_use_fqnames(dom, enforce_non_fqnames); + + /* when using the domain resolution order, using shortnames as + * input is allowed by default. However, we really want to use + * the fully qualified name as output in order to avoid + * conflicts whith users who have the very same name. */ + sss_domain_info_set_output_fqnames(cr_domain->domain, true); + + DLIST_ADD_END(cr_domains, cr_domain, + struct cache_req_domain *); + break; + } + } + } + + /* Then iterate through all the other domains (and subdomains) and add them + * to the flatten cache req domains' list */ + for (dom = domains; dom; dom = get_next_domain(dom, flag)) { + if (string_in_list(dom->name, resolution_order, false)) { + continue; + } + + files_provider = is_files_provider(dom); + + cr_domain = talloc_zero(mem_ctx, struct cache_req_domain); + if (cr_domain == NULL) { + ret = ENOMEM; + goto done; + } + cr_domain->domain = dom; + cr_domain->fqnames = + cache_req_domain_use_fqnames(dom, enforce_non_fqnames); + + /* when using the domain resolution order, using shortnames as input + * is allowed by default. However, we really want to use the fully + * qualified name as output in order to avoid conflicts whith users + * who have the very same name. + * + * NOTE: we do *not* want to use fully qualified names for the + * files provider.*/ + if (resolution_order != NULL) { + if (!files_provider) { + sss_domain_info_set_output_fqnames(cr_domain->domain, true); + } + } + + /* The implicit files provider should always be searched firstly, + * doesn't matter whether the domain_resolution_order set! + * + * By doing this we avoid querying other domains for local users. + */ + if (files_provider) { + DLIST_ADD(cr_domains, cr_domain); + continue; + } + + DLIST_ADD_END(cr_domains, cr_domain, struct cache_req_domain *); + } + + ret = EOK; + +done: + if (ret != EOK) { + cache_req_domain_list_zfree(&cr_domains); + } + + return cr_domains; +} + +errno_t +cache_req_domain_new_list_from_domain_resolution_order( + TALLOC_CTX *mem_ctx, + struct sss_domain_info *domains, + const char *domain_resolution_order, + struct cache_req_domain **_cr_domains) +{ + TALLOC_CTX *tmp_ctx; + struct cache_req_domain *cr_domains; + char **list = NULL; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + if (domain_resolution_order != NULL) { + if (strcmp(domain_resolution_order, ":") != 0) { + DEBUG(SSSDBG_TRACE_FUNC, + "Domain resolution order list (split by ':'): \"%s\"\n", + domain_resolution_order); + + ret = split_on_separator(tmp_ctx, domain_resolution_order, ':', + true, true, &list, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "split_on_separator() failed [%d]: [%s].\n", + ret, sss_strerror(ret)); + goto done; + } + } else { + DEBUG(SSSDBG_TRACE_FUNC, + "Domain resolution order list: ':' " + "(do not use any specific order)\n"); + } + } else { + DEBUG(SSSDBG_TRACE_FUNC, + "Domain resolution order list: not set\n"); + } + + cr_domains = cache_req_domain_new_list_from_string_list(mem_ctx, domains, + list); + if (cr_domains == NULL) { + ret = ENOMEM; + DEBUG(SSSDBG_OP_FAILURE, + "cache_req_domain_new_list_from_domain_resolution_order() " + "failed [%d]: [%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + *_cr_domains = cr_domains; + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} diff --git a/src/responder/common/cache_req/cache_req_domain.h b/src/responder/common/cache_req/cache_req_domain.h new file mode 100644 index 0000000..72e4995 --- /dev/null +++ b/src/responder/common/cache_req/cache_req_domain.h @@ -0,0 +1,63 @@ +/* + Authors: + Fabiano Fidêncio <fidencio@redhat.com> + + Copyright (C) 2017 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/>. +*/ + +#ifndef _CACHE_REQ_DOMAIN_H_ +#define _CACHE_REQ_DOMAIN_H_ + +#include "responder/common/responder.h" + +struct cache_req_domain { + struct sss_domain_info *domain; + bool fqnames; + bool locate_domain; + + struct cache_req_domain *prev; + struct cache_req_domain *next; +}; + +struct cache_req_domain * +cache_req_domain_get_domain_by_name(struct cache_req_domain *domains, + const char *name); + +/* + * This function may have a side effect of setting the output_fqnames' domain + * property when it's called. + * + * It happens as the output_fqnames' domain property must only be set depending + * on whether a domain resolution order is set or not, and the saner place to + * set it to all domains is when flattening those (thus, in this function). + */ +errno_t +cache_req_domain_new_list_from_domain_resolution_order( + TALLOC_CTX *mem_ctx, + struct sss_domain_info *domains, + const char *domain_resolution_order, + struct cache_req_domain **_cr_domains); + +errno_t +cache_req_domain_copy_cr_domains(TALLOC_CTX *mem_ctx, + struct cache_req_domain *src, + char **requested_domains, + struct cache_req_domain **_dest); + +void cache_req_domain_list_zfree(struct cache_req_domain **cr_domains); + + +#endif /* _CACHE_REQ_DOMAIN_H_ */ diff --git a/src/responder/common/cache_req/cache_req_plugin.h b/src/responder/common/cache_req/cache_req_plugin.h new file mode 100644 index 0000000..f86a020 --- /dev/null +++ b/src/responder/common/cache_req/cache_req_plugin.h @@ -0,0 +1,331 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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/>. +*/ + +#ifndef _CACHE_REQ_PLUGIN_H_ +#define _CACHE_REQ_PLUGIN_H_ + +#include "responder/common/cache_req/cache_req_private.h" +#include "sss_iface/sss_iface_async.h" + +enum cache_object_status { + CACHE_OBJECT_VALID, + CACHE_OBJECT_EXPIRED, + CACHE_OBJECT_MISSING, + CACHE_OBJECT_MIDPOINT +}; + +/** + * Create cache request result manually, if the searched object is well known + * and thus can not be found in the cache. + * + * + * @return EOK If it is a well known object and a result was created. + * @return ENOENT If it is not a well known object. + * @return Other errno code in case of an error. + */ +typedef errno_t +(*cache_req_is_well_known_result_fn)(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct cache_req_result **_result); + +/** + * Prepare domain data. Some plug-ins may require to alter lookup data + * per specific domain rules, such as case sensitivity, fully qualified + * format etc. + * + * @return EOK If everything went fine. + * @return Other errno code in case of an error. + */ +typedef errno_t +(*cache_req_prepare_domain_data_fn)(struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain); + +/** + * Create an object debug name that is used in debug messages to identify + * this object. + * + * @return Debug name or NULL in case of an error. + **/ +typedef const char * +(*cache_req_create_debug_name_fn)(TALLOC_CTX *mem_ctx, + struct cache_req_data *data, + struct sss_domain_info *domain); + +/** + * Check if an object is stored in negative cache. + * + * @return EOK If the object is not found. + * @return EEXIST If the object is found in negative cache. + * @return Other errno code in case of an error. + */ +typedef errno_t +(*cache_req_ncache_check_fn)(struct sss_nc_ctx *ncache, + struct sss_domain_info *domain, + struct cache_req_data *data); + +/** + * Add an object into negative cache. + * + * @return EOK If everything went fine. + * @return Other errno code in case of an error. + */ +typedef errno_t +(*cache_req_ncache_add_fn)(struct sss_nc_ctx *ncache, + struct sss_domain_info *domain, + struct cache_req_data *data); + +/** + * Filter the result through the negative cache. + * + * This is useful for plugins which don't use name as an input + * token but can be affected by filter_users and filter_groups + * options. + */ +typedef errno_t +(*cache_req_ncache_filter_fn)(struct sss_nc_ctx *ncache, + struct sss_domain_info *domain, + const char *name); + +/** + * Add an object into global negative cache. + * + * @return EOK If everything went fine. + * @return Other errno code in case of an error. + */ +typedef errno_t +(*cache_req_global_ncache_add_fn)(struct sss_nc_ctx *ncache, + struct cache_req_data *data); + +/** + * Lookup object in sysdb. + * + * @return EOK If the object is found. + * @return ENOENT If the object is not found. + * @return Other errno code in case of an error. + */ +typedef errno_t +(*cache_req_lookup_fn)(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result **_result); + +/** + * Send Data Provider request. + * + * @return Tevent request on success. + * @return NULL on error. + */ +typedef struct tevent_req * +(*cache_req_dp_send_fn)(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result *result); + +/** + * Process result of Data Provider request. + * + * Do not free subreq! It will be freed in the caller. + * + * @return True if data provider request succeeded. + * @return False if there was an error. + */ +typedef bool +(*cache_req_dp_recv_fn)(struct tevent_req *subreq, + struct cache_req *cr); + +/** + * Check whether the results of the domain locator can still + * be considered valid or whether it is time to call the request + * again. + * + * @param resp_ctx The responder context. + * @param domain The domain to check. This should be the domain-head, + * because the locator works across a domain and its + * subdomains. + * @param data The cache request data that contains primarily the key + * to look for. + * + * @return True if the locator plugin should be ran again. + * @return False if the lookup should just proceed with the + * data that is already in the negative cache. + */ +typedef bool +(*cache_req_dp_get_domain_check_fn)(struct resp_ctx *rctx, + struct sss_domain_info *domain, + struct cache_req_data *data); +/** + * Send Data Provider request to locate the domain + * of an entry + * + * @param resp_ctx The responder context. + * @param domain The domain to check. This should be the domain-head, + * because the locator works across a domain and its + * subdomains. + * @param data The cache request data that contains primarily the key + * to look for. + * + * + * @return Tevent request on success. + * @return NULL on error. + */ +typedef struct tevent_req * +(*cache_req_dp_get_domain_send_fn)(TALLOC_CTX *mem_ctx, + struct resp_ctx *rctx, + struct sss_domain_info *domain, + struct cache_req_data *data); + +/** + * Process result of Data Provider find-domain request. + * + * Do not free subreq! It will be freed in the caller. + * + * @param mem_ctx The memory context that owns the _found_domain + * result parameter. + * @param subreq The request to finish. + * @param cr The cache_req being processed. + * @param _found_domain The domain the request account belongs to. This + * parameter can be NULL even on success, in that + * case the account was not found and no lookups are + * needed, all domains can be skipped in this case. + * + * @return EOK if the request did not encounter any error. In this + * case, the _found_domain parameter can be considered authoritative, + * regarless of its value + * @return errno on error. _found_domain should be NULL in this case. + */ +typedef errno_t +(*cache_req_dp_get_domain_recv_fn)(TALLOC_CTX *mem_ctx, + struct tevent_req *subreq, + struct cache_req *cr, + char **_found_domain); + +struct cache_req_plugin { + /** + * Plugin name. + */ + const char *name; + + /** + * Expiration timestamp attribute name. + */ + const char *attr_expiration; + + /** + * Flags that are passed to get_next_domain(). + */ + uint32_t get_next_domain_flags; + + /** + * True if input name should be parsed for domain. + */ + bool parse_name; + + /** + * True if default domain suffix should be ignored when parsing name. + */ + bool ignore_default_domain; + + /** + * True if we always contact data provider. + */ + bool bypass_cache; + + /** + * True if only one result is expected. + */ + bool only_one_result; + + /** + * If true, cache request will iterate over all domains on domain-less + * search and merge acquired results. + */ + bool search_all_domains; + + /** + * True if only domains with enumeration enabled are searched. + */ + bool require_enumeration; + + /** + * Allow missing domain part even if domain requires fully qualified name + * on domain less searches. + */ + bool allow_missing_fqn; + + /** + * True if this plugin can be swapped for equivalent search with UPN. + */ + bool allow_switch_to_upn; + enum cache_req_type upn_equivalent; + + /* Operations */ + cache_req_is_well_known_result_fn is_well_known_fn; + cache_req_prepare_domain_data_fn prepare_domain_data_fn; + cache_req_create_debug_name_fn create_debug_name_fn; + cache_req_global_ncache_add_fn global_ncache_add_fn; + cache_req_ncache_check_fn ncache_check_fn; + cache_req_ncache_add_fn ncache_add_fn; + cache_req_ncache_filter_fn ncache_filter_fn; + cache_req_lookup_fn lookup_fn; + cache_req_dp_send_fn dp_send_fn; + cache_req_dp_recv_fn dp_recv_fn; + cache_req_dp_get_domain_check_fn dp_get_domain_check_fn; + cache_req_dp_get_domain_send_fn dp_get_domain_send_fn; + cache_req_dp_get_domain_recv_fn dp_get_domain_recv_fn; +}; + +extern const struct cache_req_plugin cache_req_user_by_name; +extern const struct cache_req_plugin cache_req_user_by_upn; +extern const struct cache_req_plugin cache_req_user_by_id; +extern const struct cache_req_plugin cache_req_group_by_name; +extern const struct cache_req_plugin cache_req_group_by_id; +extern const struct cache_req_plugin cache_req_initgroups_by_name; +extern const struct cache_req_plugin cache_req_initgroups_by_upn; +#ifdef BUILD_SUBID +extern const struct cache_req_plugin cache_req_subid_ranges_by_name; +#endif +extern const struct cache_req_plugin cache_req_user_by_cert; +extern const struct cache_req_plugin cache_req_user_by_filter; +extern const struct cache_req_plugin cache_req_group_by_filter; +extern const struct cache_req_plugin cache_req_object_by_sid; +extern const struct cache_req_plugin cache_req_object_by_name; +extern const struct cache_req_plugin cache_req_object_by_id; +extern const struct cache_req_plugin cache_req_enum_users; +extern const struct cache_req_plugin cache_req_enum_groups; +extern const struct cache_req_plugin cache_req_enum_svc; +extern const struct cache_req_plugin cache_req_enum_ip_hosts; +extern const struct cache_req_plugin cache_req_enum_ip_networks; +extern const struct cache_req_plugin cache_req_svc_by_name; +extern const struct cache_req_plugin cache_req_svc_by_port; +extern const struct cache_req_plugin cache_req_netgroup_by_name; +extern const struct cache_req_plugin cache_req_ssh_host_id_by_name; +extern const struct cache_req_plugin cache_req_autofs_map_entries; +extern const struct cache_req_plugin cache_req_autofs_map_by_name; +extern const struct cache_req_plugin cache_req_autofs_entry_by_name; +extern const struct cache_req_plugin cache_req_ip_host_by_name; +extern const struct cache_req_plugin cache_req_ip_host_by_addr; +extern const struct cache_req_plugin cache_req_ip_network_by_name; +extern const struct cache_req_plugin cache_req_ip_network_by_addr; + +#endif /* _CACHE_REQ_PLUGIN_H_ */ diff --git a/src/responder/common/cache_req/cache_req_private.h b/src/responder/common/cache_req/cache_req_private.h new file mode 100644 index 0000000..22f197b --- /dev/null +++ b/src/responder/common/cache_req/cache_req_private.h @@ -0,0 +1,227 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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/>. +*/ + +#ifndef _CACHE_REQ_PRIVATE_H_ +#define _CACHE_REQ_PRIVATE_H_ + +#include <stdint.h> + +#include "responder/common/responder.h" +#include "responder/common/cache_req/cache_req.h" + +#define CACHE_REQ_DEBUG(level, cr, fmt, ...) \ + DEBUG(level, "CR #%u: " fmt, (cr)->reqid, ##__VA_ARGS__) + +/* Tracing message, changing this can break log parsing tools */ +#define SSS_REQ_TRACE_CID_CR(level, cr, fmt, ...) \ + CACHE_REQ_DEBUG(level, cr, "REQ_TRACE: " fmt, ##__VA_ARGS__) + +struct cache_req { + /* Provided input. */ + struct cache_req_data *data; + + const struct cache_req_plugin *plugin; + struct resp_ctx *rctx; + struct sss_nc_ctx *ncache; + int midpoint; + + /* Domain related information. */ + struct sss_domain_info *domain; + + /* wanted cache behavior */ + enum cache_req_behavior cache_behavior; + + /* Only contact domains with this type */ + enum cache_req_dom_type req_dom_type; + + /* Debug information */ + uint32_t reqid; + const char *reqname; + const char *debugobj; + + /* Time when the request started. Useful for by-filter lookups */ + time_t req_start; +}; + +/** + * Structure to hold the information the user passed as parameter + * and some strings after processing this information. + */ +struct cache_req_parsed_name { + const char *input; /* Original input. */ + const char *name; /* Parsed name or UPN. */ + const char *lookup; /* Converted per domain rules. */ + const char *attr; /* Attribute name when looking for an attribute */ +}; + +/** + * Structure to hold the input strings that cannot contain domain + * part but are transferred per each domain's case sensitivity. + */ +struct cache_req_cased_name { + const char *name; /* Parsed name or UPN. */ + const char *lookup; /* Converted per domain rules. */ +}; + +/* Input data. */ +struct cache_req_data { + enum cache_req_type type; + struct cache_req_parsed_name name; + uint32_t id; + const char *cert; + const char *sid; + const char *alias; + const char **attrs; + const char *autofs_entry_name; + + struct { + struct cache_req_parsed_name *name; + struct cache_req_cased_name protocol; + uint16_t port; + } svc; + + struct { + uint32_t af; + uint32_t len; + uint8_t *data; + } addr; + + bool bypass_cache; + bool bypass_dp; + + /* if set, only search in the listed domains */ + char **requested_domains; + + /* if set, ERR_OFFLINE is returned if data provider is offline */ + bool propogate_offline_status; + + /* if set, only domains with MPG_HYBRID are searched */ + bool hybrid_lookup; +}; + +struct tevent_req * +cache_req_search_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct cache_req *cr, + bool first_iteration, + bool cache_only_override); + +errno_t cache_req_search_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ldb_result **_result, + bool *_dp_success); + +struct tevent_req *cache_req_locate_domain_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct cache_req *cr); +errno_t cache_req_locate_domain_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char **_found_domain); + +struct tevent_req * +cache_req_steal_data_and_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + enum cache_req_dom_type req_dom_type, + const char *domain, + struct cache_req_data *data); + +void cache_req_search_ncache_add_to_domain(struct cache_req *cr, + struct sss_domain_info *domain); + +errno_t +cache_req_add_result(TALLOC_CTX *mem_ctx, + struct cache_req_result *new_result, + struct cache_req_result ***_results, + size_t *_num_results); + +struct cache_req_result * +cache_req_create_result(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + struct ldb_result *ldb_result, + const char *lookup_name, + const char *well_known_domain); + +errno_t +cache_req_create_and_add_result(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct sss_domain_info *domain, + struct ldb_result *ldb_result, + const char *name, + struct cache_req_result ***_results, + size_t *_num_results); + +struct ldb_result * +cache_req_create_ldb_result_from_msg_list(TALLOC_CTX *mem_ctx, + struct ldb_message **ldb_msgs, + size_t ldb_msg_count); + +struct ldb_result * +cache_req_create_ldb_result_from_msg(TALLOC_CTX *mem_ctx, + struct ldb_message *ldb_msg); + +struct cache_req_result * +cache_req_create_result_from_msg(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + struct ldb_message *ldb_msg, + const char *lookup_name, + const char *well_known_domain); + +struct tevent_req * +cache_req_sr_overlay_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct cache_req *cr, + struct cache_req_result **results, + size_t num_results); + +errno_t +cache_req_sr_overlay_recv(struct tevent_req *req); + +/* Plug-in common. */ + +struct cache_req_result * +cache_req_well_known_sid_result(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + const char *domname, + const char *sid, + const char *name); + +bool +cache_req_common_process_dp_reply(struct cache_req *cr, + errno_t ret, + uint16_t err_maj, + uint32_t err_min, + const char *err_msg); + +bool +cache_req_common_dp_recv(struct tevent_req *subreq, + struct cache_req *cr); + +errno_t +cache_req_common_get_acct_domain_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *subreq, + struct cache_req *cr, + char **_domain); + +errno_t cache_req_idminmax_check(struct cache_req_data *data, + struct sss_domain_info *domain); +#endif /* _CACHE_REQ_PRIVATE_H_ */ diff --git a/src/responder/common/cache_req/cache_req_result.c b/src/responder/common/cache_req/cache_req_result.c new file mode 100644 index 0000000..c1a3732 --- /dev/null +++ b/src/responder/common/cache_req/cache_req_result.c @@ -0,0 +1,274 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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 <ldb.h> +#include <talloc.h> +#include <errno.h> + +#include "util/util.h" +#include "responder/common/cache_req/cache_req_private.h" + +errno_t +cache_req_add_result(TALLOC_CTX *mem_ctx, + struct cache_req_result *new_result, + struct cache_req_result ***_results, + size_t *_num_results) +{ + struct cache_req_result **results = *_results; + size_t idx; + size_t count; + + /* Make space for new results. */ + idx = *_num_results; + count = *_num_results + 1; + + results = talloc_realloc(mem_ctx, results, struct cache_req_result *, + count + 1); + if (results == NULL) { + return ENOMEM; + } + + results[idx] = talloc_steal(results, new_result); + results[idx + 1] = NULL; + + *_results = results; + *_num_results = count; + + return EOK; +} + +struct cache_req_result * +cache_req_create_result(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + struct ldb_result *ldb_result, + const char *lookup_name, + const char *well_known_domain) +{ + struct cache_req_result *result; + + result = talloc_zero(mem_ctx, struct cache_req_result); + if (result == NULL) { + return NULL; + } + + result->domain = domain; + result->ldb_result = talloc_steal(result, ldb_result); + result->count = ldb_result != NULL ? ldb_result->count : 0; + result->msgs = ldb_result != NULL ? ldb_result->msgs : NULL; + + if (lookup_name != NULL) { + result->lookup_name = talloc_strdup(result, lookup_name); + if (result->lookup_name == NULL) { + talloc_free(result); + return NULL; + } + } + + if (well_known_domain != NULL) { + result->well_known_domain = talloc_strdup(result, well_known_domain); + if (result->well_known_domain == NULL) { + talloc_free(result); + return NULL; + } + } + + return result; +} + +errno_t +cache_req_create_and_add_result(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct sss_domain_info *domain, + struct ldb_result *ldb_result, + const char *name, + struct cache_req_result ***_results, + size_t *_num_results) +{ + struct cache_req_result *item; + errno_t ret; + + CACHE_REQ_DEBUG(SSSDBG_TRACE_FUNC, cr, + "Found %u entries in domain %s\n", + ldb_result->count, domain->name); + + item = cache_req_create_result(mem_ctx, domain, ldb_result, name, NULL); + if (item == NULL) { + return ENOMEM; + } + + ret = cache_req_add_result(mem_ctx, item, _results, _num_results); + if (ret != EOK) { + talloc_free(item); + } + + return ret; +} + +struct ldb_result * +cache_req_create_ldb_result_from_msg_list(TALLOC_CTX *mem_ctx, + struct ldb_message **ldb_msgs, + size_t ldb_msg_count) +{ + struct ldb_result *ldb_result; + + if (ldb_msgs == NULL || ldb_msgs[0] == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No message set!\n"); + return NULL; + } + + ldb_result = talloc_zero(NULL, struct ldb_result); + if (ldb_result == NULL) { + return NULL; + } + + ldb_result->extended = NULL; + ldb_result->controls = NULL; + ldb_result->refs = NULL; + ldb_result->count = ldb_msg_count; + ldb_result->msgs = talloc_zero_array(ldb_result, struct ldb_message *, + ldb_msg_count + 1); + if (ldb_result->msgs == NULL) { + talloc_free(ldb_result); + return NULL; + } + + for (size_t i = 0; i < ldb_msg_count; i++) { + ldb_result->msgs[i] = talloc_steal(ldb_result->msgs, ldb_msgs[i]); + } + + return ldb_result; +} + +struct ldb_result * +cache_req_create_ldb_result_from_msg(TALLOC_CTX *mem_ctx, + struct ldb_message *ldb_msg) +{ + struct ldb_result *ldb_result; + + if (ldb_msg == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No message set!\n"); + return NULL; + } + + ldb_result = talloc_zero(NULL, struct ldb_result); + if (ldb_result == NULL) { + return NULL; + } + + ldb_result->extended = NULL; + ldb_result->controls = NULL; + ldb_result->refs = NULL; + ldb_result->count = 1; + ldb_result->msgs = talloc_zero_array(ldb_result, struct ldb_message *, 2); + if (ldb_result->msgs == NULL) { + talloc_free(ldb_result); + return NULL; + } + + ldb_result->msgs[0] = talloc_steal(ldb_result->msgs, ldb_msg); + + return ldb_result; +} + +struct cache_req_result * +cache_req_create_result_from_msg(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + struct ldb_message *ldb_msg, + const char *lookup_name, + const char *well_known_domain) +{ + struct cache_req_result *result; + struct ldb_result *ldb_result; + + if (ldb_msg == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No message set!\n"); + return NULL; + } + + ldb_result = cache_req_create_ldb_result_from_msg(mem_ctx, ldb_msg); + if (ldb_result == NULL) { + return NULL; + } + + result = cache_req_create_result(mem_ctx, domain, ldb_result, + lookup_name, well_known_domain); + if (result == NULL) { + talloc_free(ldb_result); + return NULL; + } + + return result; +} + +struct cache_req_result * +cache_req_copy_limited_result(TALLOC_CTX *mem_ctx, + struct cache_req_result *result, + uint32_t start, + uint32_t limit) +{ + struct cache_req_result *out = NULL; + struct ldb_result *ldb_result; + unsigned int left; + errno_t ret; + + if (start >= result->count) { + ret = ERANGE; + goto done; + } + + out = talloc_zero(mem_ctx, struct cache_req_result); + if (out == NULL) { + ret = ENOMEM; + goto done; + } + + ldb_result = talloc_zero(out, struct ldb_result); + if (ldb_result == NULL) { + ret = ENOMEM; + goto done; + } + + left = result->count - start; + + ldb_result->extended = result->ldb_result->extended; + ldb_result->controls = result->ldb_result->controls; + ldb_result->refs = result->ldb_result->refs; + ldb_result->msgs = &(result->ldb_result->msgs[start]); + ldb_result->count = left < limit ? left : limit; + + out->domain = result->domain; + out->ldb_result = ldb_result; + out->lookup_name = result->lookup_name; + out->count = ldb_result->count; + out->msgs = ldb_result->msgs; + + ret = EOK; + +done: + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create cache request result " + "[%d]: %s\n", ret, sss_strerror(ret)); + + talloc_free(out); + return NULL; + } + + return out; +} diff --git a/src/responder/common/cache_req/cache_req_search.c b/src/responder/common/cache_req/cache_req_search.c new file mode 100644 index 0000000..d800f47 --- /dev/null +++ b/src/responder/common/cache_req/cache_req_search.c @@ -0,0 +1,643 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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 <ldb.h> +#include <talloc.h> +#include <tevent.h> + +#include "util/util.h" +#include "responder/common/cache_req/cache_req_private.h" +#include "responder/common/cache_req/cache_req_plugin.h" +#include "db/sysdb.h" + +static errno_t cache_req_search_ncache(struct cache_req *cr) +{ + errno_t ret; + + if (cr->plugin->ncache_check_fn == NULL) { + CACHE_REQ_DEBUG(SSSDBG_TRACE_INTERNAL, cr, + "This request type does not support negative cache\n"); + return EOK; + } + + CACHE_REQ_DEBUG(SSSDBG_TRACE_FUNC, cr, + "Checking negative cache for [%s]\n", + cr->debugobj); + + ret = cr->plugin->ncache_check_fn(cr->ncache, cr->domain, cr->data); + if (ret == EEXIST) { + CACHE_REQ_DEBUG(SSSDBG_TRACE_FUNC, cr, + "[%s] does not exist (negative cache)\n", + cr->debugobj); + return ENOENT; + } else if (ret != EOK && ret != ENOENT) { + CACHE_REQ_DEBUG(SSSDBG_CRIT_FAILURE, cr, + "Unable to check negative cache [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + CACHE_REQ_DEBUG(SSSDBG_TRACE_FUNC, cr, + "[%s] is not present in negative cache\n", + cr->debugobj); + + return EOK; +} + +void cache_req_search_ncache_add_to_domain(struct cache_req *cr, + struct sss_domain_info *domain) +{ + errno_t ret; + + if (cr->plugin->ncache_add_fn == NULL) { + CACHE_REQ_DEBUG(SSSDBG_TRACE_INTERNAL, cr, + "This request type does not support negative cache\n"); + return; + } + + CACHE_REQ_DEBUG(SSSDBG_TRACE_FUNC, cr, "Adding [%s] to negative cache\n", + cr->debugobj); + + ret = cr->plugin->ncache_add_fn(cr->ncache, domain, cr->data); + if (ret != EOK) { + CACHE_REQ_DEBUG(SSSDBG_MINOR_FAILURE, cr, + "Cannot set negative cache for [%s] [%d]: %s\n", + cr->debugobj, ret, sss_strerror(ret)); + /* not fatal */ + } + + return; +} + +static void cache_req_search_ncache_add(struct cache_req *cr) +{ + return cache_req_search_ncache_add_to_domain(cr, cr->domain); +} + +static errno_t cache_req_search_ncache_filter(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct ldb_result **_result) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_result *filtered_result; + struct ldb_message **msgs; + size_t msg_count; + const char *name; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + if (cr->plugin->ncache_filter_fn == NULL) { + CACHE_REQ_DEBUG(SSSDBG_TRACE_FUNC, cr, + "This request type does not support filtering " + "result by negative cache\n"); + + ret = EOK; + goto done; + } + + CACHE_REQ_DEBUG(SSSDBG_TRACE_FUNC, cr, + "Filtering out results by negative cache\n"); + + msgs = talloc_zero_array(tmp_ctx, struct ldb_message *, (*_result)->count); + msg_count = 0; + + for (size_t i = 0; i < (*_result)->count; i++) { + name = sss_get_name_from_msg(cr->domain, (*_result)->msgs[i]); + if (name == NULL) { + CACHE_REQ_DEBUG(SSSDBG_CRIT_FAILURE, cr, + "sss_get_name_from_msg() returned NULL, which should never " + "happen in this scenario!\n"); + ret = ERR_INTERNAL; + goto done; + } + + ret = cr->plugin->ncache_filter_fn(cr->ncache, cr->domain, name); + if (ret == EEXIST) { + CACHE_REQ_DEBUG(SSSDBG_TRACE_FUNC, cr, + "[%s] filtered out! (negative cache)\n", + name); + continue; + } else if (ret != EOK && ret != ENOENT) { + CACHE_REQ_DEBUG(SSSDBG_CRIT_FAILURE, cr, + "Unable to check negative cache [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + msgs[msg_count] = talloc_steal(msgs, (*_result)->msgs[i]); + msg_count++; + } + + if (msg_count == 0) { + ret = ENOENT; + goto done; + } + + filtered_result = cache_req_create_ldb_result_from_msg_list(tmp_ctx, msgs, + msg_count); + if (filtered_result == NULL) { + ret = ENOMEM; + goto done; + } + + talloc_zfree(*_result); + *_result = talloc_steal(mem_ctx, filtered_result); + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static int +cache_req_should_be_in_cache(struct cache_req *cr, + struct ldb_result *result) +{ + id_t id = 0; + + if (result == NULL || result->count != 1) { + /* can't decide so keep it */ + return EOK; + } + + id = ldb_msg_find_attr_as_uint(result->msgs[0], SYSDB_UIDNUM, 0); + if (id && OUT_OF_ID_RANGE(id, cr->domain->id_min, cr->domain->id_max)) { + return ERR_ID_OUTSIDE_RANGE; + } + + id = ldb_msg_find_attr_as_uint(result->msgs[0], SYSDB_GIDNUM, 0); + if (id && OUT_OF_ID_RANGE(id, cr->domain->id_min, cr->domain->id_max)) { + return ERR_ID_OUTSIDE_RANGE; + } + + return EOK; +} + +static errno_t cache_req_search_cache(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct ldb_result **_result) +{ + struct ldb_result *result = NULL; + errno_t ret; + + if (cr->plugin->lookup_fn == NULL) { + CACHE_REQ_DEBUG(SSSDBG_CRIT_FAILURE, cr, + "Bug: No cache lookup function specified\n"); + return ERR_INTERNAL; + } + + CACHE_REQ_DEBUG(SSSDBG_TRACE_FUNC, cr, + "Looking up [%s] in cache\n", + cr->debugobj); + + ret = cr->plugin->lookup_fn(mem_ctx, cr, cr->data, cr->domain, &result); + if (ret == EOK && (result == NULL || result->count == 0)) { + ret = ENOENT; + } + + if (ret == EOK) { + ret = cache_req_should_be_in_cache(cr, result); + } + + switch (ret) { + case EOK: + if (cr->plugin->only_one_result && result->count > 1) { + CACHE_REQ_DEBUG(SSSDBG_CRIT_FAILURE, cr, + "Multiple objects were found when " + "only one was expected!\n"); + ret = ERR_MULTIPLE_ENTRIES; + goto done; + } + + *_result = result; + break; + case ERR_ID_OUTSIDE_RANGE: + CACHE_REQ_DEBUG(SSSDBG_TRACE_FUNC, cr, + "ID [%s] was filtered out\n", + cr->debugobj); + break; + case ENOENT: + CACHE_REQ_DEBUG(SSSDBG_TRACE_FUNC, cr, + "Object [%s] was not found in cache\n", + cr->debugobj); + break; + default: + CACHE_REQ_DEBUG(SSSDBG_CRIT_FAILURE, cr, + "Unable to lookup [%s] in cache [%d]: %s\n", + cr->debugobj, ret, sss_strerror(ret)); + break; + } + +done: + if (ret != EOK) { + talloc_free(result); + } + + return ret; +} + +static enum cache_object_status +cache_req_expiration_status(struct cache_req *cr, + struct ldb_result *result) +{ + time_t expire; + errno_t ret; + + if (result == NULL || result->count == 0 || cr->plugin->bypass_cache) { + return CACHE_OBJECT_MISSING; + } + + expire = ldb_msg_find_attr_as_uint64(result->msgs[0], + cr->plugin->attr_expiration, 0); + + ret = sss_cmd_check_cache(result->msgs[0], cr->midpoint, expire); + if (ret == EOK) { + return CACHE_OBJECT_VALID; + } else if (ret == EAGAIN) { + return CACHE_OBJECT_MIDPOINT; + } + + return CACHE_OBJECT_EXPIRED; +} + +struct cache_req_search_state { + /* input data */ + struct tevent_context *ev; + struct resp_ctx *rctx; + struct cache_req *cr; + + /* output data */ + struct ldb_result *result; + bool dp_success; +}; + +static errno_t cache_req_search_dp(struct tevent_req *req, + enum cache_object_status status); +static void cache_req_search_oob_done(struct tevent_req *subreq); +static void cache_req_search_done(struct tevent_req *subreq); + +struct tevent_req * +cache_req_search_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct cache_req *cr, + bool first_iteration, + bool cache_only_override) +{ + struct cache_req_search_state *state; + enum cache_object_status status; + struct tevent_req *req; + bool bypass_cache = false; + bool bypass_dp = false; + bool skip_refresh = false; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct cache_req_search_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + CACHE_REQ_DEBUG(SSSDBG_TRACE_FUNC, cr, "Looking up %s\n", cr->debugobj); + + state->ev = ev; + state->cr = cr; + + ret = cache_req_search_ncache(cr); + if (ret != EOK) { + goto done; + } + + if (cache_only_override) { + bypass_dp = true; + } else { + switch (cr->cache_behavior) { + case CACHE_REQ_CACHE_FIRST: + bypass_cache = first_iteration ? false : true; + bypass_dp = first_iteration ? true : false; + break; + case CACHE_REQ_BYPASS_CACHE: + bypass_cache = true; + break; + case CACHE_REQ_BYPASS_PROVIDER: + bypass_dp = true; + skip_refresh = true; + break; + default: + break; + } + } + + /* If bypass_cache is enabled we always contact data provider before + * searching the cache. Thus we set expiration status to missing, + * which will trigger data provider request later. + * + * If disabled, we want to search the cache here to see if the + * object is already cached and valid or if data provider needs + * to be contacted. + */ + state->result = NULL; + status = CACHE_OBJECT_MISSING; + if (!bypass_cache) { + ret = cache_req_search_cache(state, cr, &state->result); + if (ret != EOK && ret != ENOENT) { + goto done; + } + + status = cache_req_expiration_status(cr, state->result); + if (status == CACHE_OBJECT_VALID) { + CACHE_REQ_DEBUG(SSSDBG_TRACE_FUNC, cr, + "Returning [%s] from cache\n", cr->debugobj); + ret = EOK; + goto done; + } + + /* For the CACHE_REQ_CACHE_FIRST case, if bypass_dp is true but we + * found the object in this domain, we will contact the data provider + * anyway to refresh it so we can return it without searching the rest + * of the domains. + */ + if (status != CACHE_OBJECT_MISSING && !skip_refresh) { + CACHE_REQ_DEBUG(SSSDBG_TRACE_FUNC, cr, + "Object found, but needs to be refreshed.\n"); + bypass_dp = false; + } else { + ret = ENOENT; + } + } + + if (!bypass_dp) { + ret = cache_req_search_dp(req, status); + } + + if (ret != EAGAIN) { + goto done; + } + + return req; + +done: + if (ret == EOK) { + ret = cache_req_search_ncache_filter(state, cr, &state->result); + } + + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static errno_t cache_req_search_dp(struct tevent_req *req, + enum cache_object_status status) +{ + struct cache_req_search_state *state; + struct tevent_req *subreq; + errno_t ret; + + state = tevent_req_data(req, struct cache_req_search_state); + + switch (status) { + case CACHE_OBJECT_MIDPOINT: + /* Out of band update. The calling function will return the cached + * entry immediately. We need to use rctx so the request is not + * removed when state is freed. */ + + CACHE_REQ_DEBUG(SSSDBG_TRACE_FUNC, state->cr, + "Performing midpoint cache update of [%s]\n", + state->cr->debugobj); + + subreq = state->cr->plugin->dp_send_fn(state->rctx, state->cr, + state->cr->data, + state->cr->domain, + state->result); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory sending out-of-band " + "data provider request\n"); + /* This is non-fatal, so we'll continue here */ + } else { + tevent_req_set_callback(subreq, cache_req_search_oob_done, req); + } + + ret = EOK; + break; + case CACHE_OBJECT_EXPIRED: + case CACHE_OBJECT_MISSING: + CACHE_REQ_DEBUG(SSSDBG_TRACE_FUNC, state->cr, + "Looking up [%s] in data provider\n", + state->cr->debugobj); + + subreq = state->cr->plugin->dp_send_fn(state->cr, state->cr, + state->cr->data, + state->cr->domain, + state->result); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Out of memory sending data provider request\n"); + ret = ENOMEM; + break; + } + + tevent_req_set_callback(subreq, cache_req_search_done, req); + ret = EAGAIN; + break; + default: + /* error */ + CACHE_REQ_DEBUG(SSSDBG_CRIT_FAILURE, state->cr, + "Unexpected status [%d]\n", status); + ret = ERR_INTERNAL; + break; + } + + return ret; +} + +static void cache_req_search_oob_done(struct tevent_req *subreq) +{ + DEBUG(SSSDBG_TRACE_INTERNAL, "Out of band request finished\n"); + talloc_zfree(subreq); + + return; +} + +static void cache_req_search_done(struct tevent_req *subreq) +{ + struct cache_req_search_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct cache_req_search_state); + + state->dp_success = state->cr->plugin->dp_recv_fn(subreq, state->cr); + talloc_zfree(subreq); + + /* Do not try to read from cache if the domain is inconsistent */ + if (sss_domain_get_state(state->cr->domain) == DOM_INCONSISTENT) { + CACHE_REQ_DEBUG(SSSDBG_TRACE_FUNC, state->cr, "Domain inconsistent, " + "we will not return cached data\n"); + + ret = ENOENT; + goto done; + } + + /* Get result from cache again. */ + ret = cache_req_search_cache(state, state->cr, &state->result); + if (ret != EOK) { + if (ret == ENOENT) { + /* Only store entry in negative cache if DP request succeeded + * because only then we know that the entry does not exist. */ + if (state->dp_success) { + cache_req_search_ncache_add(state->cr); + } + } + goto done; + } + + /* ret == EOK */ + ret = cache_req_search_ncache_filter(state, state->cr, &state->result); + if (ret != EOK) { + goto done; + } + + CACHE_REQ_DEBUG(SSSDBG_TRACE_FUNC, state->cr, + "Returning updated object [%s]\n", state->cr->debugobj); + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +errno_t cache_req_search_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ldb_result **_result, + bool *_dp_success) +{ + struct cache_req_search_state *state = NULL; + state = tevent_req_data(req, struct cache_req_search_state); + + *_dp_success = state->dp_success; + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_result = talloc_steal(mem_ctx, state->result); + + return EOK; +} + +struct cache_req_locate_domain_state { + struct cache_req *cr; + + char *found_domain; +}; + +static void cache_req_locate_domain_done(struct tevent_req *subreq); + +struct tevent_req *cache_req_locate_domain_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct cache_req *cr) +{ + struct cache_req_locate_domain_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + errno_t ret; + bool should_run; + + req = tevent_req_create(mem_ctx, &state, struct cache_req_locate_domain_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + state->cr = cr; + + should_run = cr->plugin->dp_get_domain_check_fn(cr->rctx, + get_domains_head(cr->domain), + cr->data); + if (should_run == false) { + /* The request was tried too recently, don't issue a new one + * as its results are still valid + */ + ret = ERR_GET_ACCT_DOM_CACHED; + goto immediate; + } + + subreq = cr->plugin->dp_get_domain_send_fn(state, + cr->rctx, + get_domains_head(cr->domain), + cr->data); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, cache_req_locate_domain_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void cache_req_locate_domain_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct cache_req_locate_domain_state *state; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct cache_req_locate_domain_state); + + ret = state->cr->plugin->dp_get_domain_recv_fn(state, + subreq, + state->cr, + &state->found_domain); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t cache_req_locate_domain_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char **_found_domain) +{ + struct cache_req_locate_domain_state *state = NULL; + + state = tevent_req_data(req, struct cache_req_locate_domain_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_found_domain = talloc_steal(mem_ctx, state->found_domain); + return EOK; +} diff --git a/src/responder/common/cache_req/cache_req_sr_overlay.c b/src/responder/common/cache_req/cache_req_sr_overlay.c new file mode 100644 index 0000000..8deb06a --- /dev/null +++ b/src/responder/common/cache_req/cache_req_sr_overlay.c @@ -0,0 +1,347 @@ +/* + Authors: + Nikolai Kondrashov <Nikolai.Kondrashov@redhat.com> + + Copyright (C) 2017 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 "db/sysdb.h" +#include "responder/common/cache_req/cache_req_private.h" + +struct cache_req_sr_overlay_state { + /* Input data */ + struct tevent_context *ev; + struct cache_req *cr; + struct cache_req_result **results; + size_t num_results; + /* Work data */ + size_t res_idx; + size_t msg_idx; +}; + +static errno_t cache_req_sr_overlay_match_users( + struct cache_req_sr_overlay_state *state); + +static errno_t cache_req_sr_overlay_match_users( + struct cache_req_sr_overlay_state *state); + +static struct tevent_req *cache_req_sr_overlay_match_all_step_send( + struct cache_req_sr_overlay_state *state); + +static void cache_req_sr_overlay_match_all_step_done( + struct tevent_req *subreq); + +struct tevent_req *cache_req_sr_overlay_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct cache_req *cr, + struct cache_req_result **results, + size_t num_results) +{ + errno_t ret = EOK; + struct tevent_req *req; + struct tevent_req *subreq; + struct cache_req_sr_overlay_state *state; + struct resp_ctx *rctx = cr->rctx; + + req = tevent_req_create(mem_ctx, &state, + struct cache_req_sr_overlay_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->cr = cr; + state->results = results; + state->num_results = num_results; + + /* If session recording is selective */ + if (rctx->sr_conf.scope != SESSION_RECORDING_SCOPE_NONE) { + /* If it's a request for a user/users */ + switch (cr->data->type) { + case CACHE_REQ_USER_BY_NAME: + case CACHE_REQ_USER_BY_UPN: + case CACHE_REQ_USER_BY_ID: + case CACHE_REQ_ENUM_USERS: + /* If we have group names to match against */ + if ((rctx->sr_conf.groups != NULL && + rctx->sr_conf.groups[0] != NULL) || + (rctx->sr_conf.exclude_groups != NULL && + rctx->sr_conf.exclude_groups[0] != NULL)) { + /* Pull and match group and user names for each user entry */ + subreq = cache_req_sr_overlay_match_all_step_send(state); + if (subreq == NULL) { + CACHE_REQ_DEBUG(SSSDBG_CRIT_FAILURE, state->cr, + "Failed allocating a session recording " + "user overlay request\n"); + ret = ENOMEM; + goto done; + } + tevent_req_set_callback( + subreq, cache_req_sr_overlay_match_all_step_done, req); + ret = EAGAIN; + } else { + /* Only match user names for each user entry */ + ret = cache_req_sr_overlay_match_users(state); + } + break; + default: + break; + } + } + +done: + if (ret != EAGAIN) { + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + } + + return req; +} + +static errno_t cache_req_sr_overlay_match_users( + struct cache_req_sr_overlay_state *state) +{ + struct cache_req *cr; + struct resp_ctx *rctx; + errno_t ret; + int lret; + TALLOC_CTX *tmp_ctx = NULL; + struct cache_req_result *result; + struct ldb_message *msg; + const char *name; + char *output_name; + char **conf_user; + char **conf_exclude_user; + bool enabled; + char *enabled_str; + + cr = state->cr; + rctx = cr->rctx; + + /* Create per-message talloc context */ + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + CACHE_REQ_DEBUG(SSSDBG_CRIT_FAILURE, cr, + "Failed creating temporary talloc context\n"); + ret = ENOMEM; + goto done; + } + + /* For each result */ + for (state->res_idx = 0; + state->res_idx < state->num_results; + state->res_idx++) { + result = state->results[state->res_idx]; + + /* For each message */ + for (state->msg_idx = 0; + state->msg_idx < result->count; + state->msg_idx++) { + msg = result->msgs[state->msg_idx]; + + /* Format output username */ + name = sss_get_name_from_msg(result->domain, msg); + ret = sss_output_fqname(tmp_ctx, result->domain, name, + rctx->override_space, + &output_name); + if (ret != EOK) { + CACHE_REQ_DEBUG(SSSDBG_CRIT_FAILURE, cr, + "Failed formatting output username from %s: %s\n", + name, sss_strerror(ret)); + goto done; + } + + /* For each user name in session recording config */ + enabled = false; + conf_user = rctx->sr_conf.users; + if (rctx->sr_conf.scope == SESSION_RECORDING_SCOPE_SOME) { + if (conf_user != NULL) { + for (; *conf_user != NULL; conf_user++) { + /* If it matches the requested user name */ + if (strcmp(*conf_user, output_name) == 0) { + enabled = true; + break; + } + } + } + /* For each exclude user name in session recording config */ + } else if (rctx->sr_conf.scope == SESSION_RECORDING_SCOPE_ALL) { + enabled = true; + conf_exclude_user = rctx->sr_conf.exclude_users; + if (conf_exclude_user != NULL) { + for (; *conf_exclude_user != NULL; conf_exclude_user++) { + /* If it matches the requested user name */ + if (strcmp(*conf_exclude_user, output_name) == 0) { + enabled = false; + break; + } + } + } + } + + /* Set sessionRecording attribute to enabled value */ + ldb_msg_remove_attr(msg, SYSDB_SESSION_RECORDING); + enabled_str = talloc_strdup(tmp_ctx, enabled ? "TRUE" : "FALSE"); + if (enabled_str == NULL) { + CACHE_REQ_DEBUG(SSSDBG_CRIT_FAILURE, cr, + "Failed to allocate a %s attribute value\n", + SYSDB_SESSION_RECORDING); + ret = ENOMEM; + goto done; + } + lret = ldb_msg_add_string(msg, SYSDB_SESSION_RECORDING, enabled_str); + if (lret != LDB_SUCCESS) { + ret = sss_ldb_error_to_errno(lret); + CACHE_REQ_DEBUG(SSSDBG_CRIT_FAILURE, cr, + "Failed adding %s attribute: %s\n", + SYSDB_SESSION_RECORDING, sss_strerror(ret)); + goto done; + } + talloc_steal(msg, enabled_str); + + /* Free per-message allocations */ + talloc_free_children(tmp_ctx); + } + } + + ret = EOK; + +done: + talloc_zfree(tmp_ctx); + return ret; +} + +static struct tevent_req *cache_req_sr_overlay_match_all_step_send( + struct cache_req_sr_overlay_state *state) +{ + struct cache_req *cr = state->cr; + struct cache_req_result *result = + state->results[state->res_idx]; + const char *name; + + name = ldb_msg_find_attr_as_string(result->msgs[state->msg_idx], + SYSDB_NAME, NULL); + return cache_req_initgr_by_name_send(state, state->ev, cr->rctx, cr->ncache, + cr->midpoint, CACHE_REQ_ANY_DOM, + NULL, name); +} + +static void cache_req_sr_overlay_match_all_step_done( + struct tevent_req *subreq) +{ + int lret; + errno_t ret; + TALLOC_CTX *tmp_ctx = NULL; + struct tevent_req *req; + struct cache_req_sr_overlay_state *state; + struct cache_req_result *result; + struct ldb_message *msg; + const char *enabled; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct cache_req_sr_overlay_state); + msg = state->results[state->res_idx]-> + msgs[state->msg_idx]; + + /* Create temporary allocation context */ + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + CACHE_REQ_DEBUG(SSSDBG_CRIT_FAILURE, state->cr, + "Failed creating temporary talloc context\n"); + ret = ENOMEM; + goto done; + } + + /* Get initgroups result */ + ret = cache_req_initgr_by_name_recv(tmp_ctx, subreq, &result); + talloc_zfree(subreq); + if (ret != EOK) { + CACHE_REQ_DEBUG(SSSDBG_CRIT_FAILURE, state->cr, + "Failed retrieving initgr request results: %s\n", + sss_strerror(ret)); + goto done; + } + + /* Overwrite sessionRecording attribute */ + ldb_msg_remove_attr(msg, SYSDB_SESSION_RECORDING); + enabled = ldb_msg_find_attr_as_string(result->msgs[0], + SYSDB_SESSION_RECORDING, NULL); + if (enabled != NULL) { + char *enabled_copy; + enabled_copy = talloc_strdup(tmp_ctx, enabled); + if (enabled_copy == NULL) { + CACHE_REQ_DEBUG(SSSDBG_CRIT_FAILURE, state->cr, + "Failed to allocate a copy of %s attribute\n", + SYSDB_SESSION_RECORDING); + ret = ENOMEM; + goto done; + } + lret = ldb_msg_add_string(msg, SYSDB_SESSION_RECORDING, enabled_copy); + if (lret != LDB_SUCCESS) { + ret = sss_ldb_error_to_errno(lret); + CACHE_REQ_DEBUG(SSSDBG_CRIT_FAILURE, state->cr, + "Failed adding %s attribute: %s\n", + SYSDB_SESSION_RECORDING, sss_strerror(ret)); + goto done; + } + talloc_steal(msg, enabled_copy); + } + + /* Move onto next entry, if any */ + state->msg_idx++; + if (state->msg_idx >= + state->results[state->res_idx]->count) { + state->res_idx++; + if (state->res_idx >= state->num_results) { + ret = EOK; + goto done; + } + state->msg_idx = 0; + } + + /* Schedule next entry overlay */ + subreq = cache_req_sr_overlay_match_all_step_send(state); + if (subreq == NULL) { + ret = ENOMEM; + CACHE_REQ_DEBUG(SSSDBG_CRIT_FAILURE, state->cr, + "Failed allocating a session recording " + "user overlay request\n"); + goto done; + } + tevent_req_set_callback(subreq, + cache_req_sr_overlay_match_all_step_done, req); + ret = EAGAIN; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } + talloc_free(tmp_ctx); +} + +errno_t cache_req_sr_overlay_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} diff --git a/src/responder/common/cache_req/plugins/cache_req_autofs_entry_by_name.c b/src/responder/common/cache_req/plugins/cache_req_autofs_entry_by_name.c new file mode 100644 index 0000000..b2b0a06 --- /dev/null +++ b/src/responder/common/cache_req/plugins/cache_req_autofs_entry_by_name.c @@ -0,0 +1,161 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2019 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 <talloc.h> +#include <ldb.h> + +#include "db/sysdb.h" +#include "db/sysdb_autofs.h" +#include "util/util.h" +#include "util/sss_chain_id.h" +#include "providers/data_provider.h" +#include "responder/common/cache_req/cache_req_plugin.h" + +static const char * +cache_req_autofs_entry_by_name_create_debug_name(TALLOC_CTX *mem_ctx, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + return talloc_asprintf(mem_ctx, "%s:%s", + data->name.name, + data->autofs_entry_name); +} + +static errno_t +cache_req_autofs_entry_by_name_lookup(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result **_result) +{ + struct ldb_message *entry; + struct ldb_result *result; + errno_t ret; + + ret = sysdb_get_autofsentry(mem_ctx, domain, data->name.name, + data->autofs_entry_name, &entry); + if (ret != EOK) { + return ret; + } + + result = cache_req_create_ldb_result_from_msg(mem_ctx, entry); + if (result == NULL) { + talloc_free(entry); + return ENOMEM; + } + + *_result = talloc_steal(mem_ctx, result); + return EOK; +} + +static struct tevent_req * +cache_req_autofs_entry_by_name_dp_send(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result *result) +{ + struct be_conn *be_conn; + errno_t ret; + + ret = sss_dp_get_domain_conn(cr->rctx, domain->conn_name, &be_conn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "BUG: The Data Provider connection for %s is not available!\n", + domain->name); + return NULL; + } + + return sbus_call_dp_autofs_GetEntry_send(mem_ctx, be_conn->conn, + be_conn->bus_name, SSS_BUS_PATH, + 0, data->name.name, + data->autofs_entry_name, + sss_chain_id_get()); +} + +bool +cache_req_autofs_entry_by_name_dp_recv(struct tevent_req *subreq, + struct cache_req *cr) +{ + errno_t ret; + + ret = sbus_call_dp_autofs_GetEntry_recv(subreq); + + if (ret == ERR_MISSING_DP_TARGET || ret == ENOENT) { + ret = EOK; + } + + return ret == EOK; +} + +const struct cache_req_plugin cache_req_autofs_entry_by_name = { + .name = "Get autofs entry", + .attr_expiration = SYSDB_CACHE_EXPIRE, + .parse_name = true, + .ignore_default_domain = true, + .bypass_cache = false, + .only_one_result = false, + .search_all_domains = false, + .require_enumeration = false, + .allow_missing_fqn = true, + .allow_switch_to_upn = false, + .upn_equivalent = CACHE_REQ_SENTINEL, + .get_next_domain_flags = 0, + + .is_well_known_fn = NULL, + .prepare_domain_data_fn = NULL, + .create_debug_name_fn = cache_req_autofs_entry_by_name_create_debug_name, + .global_ncache_add_fn = NULL, + .ncache_check_fn = NULL, + .ncache_add_fn = NULL, + .ncache_filter_fn = NULL, + .lookup_fn = cache_req_autofs_entry_by_name_lookup, + .dp_send_fn = cache_req_autofs_entry_by_name_dp_send, + .dp_recv_fn = cache_req_autofs_entry_by_name_dp_recv, + .dp_get_domain_check_fn = NULL, + .dp_get_domain_send_fn = NULL, + .dp_get_domain_recv_fn = NULL, +}; + +struct tevent_req * +cache_req_autofs_entry_by_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + const char *domain, + const char *mapname, + const char *entryname) +{ + struct cache_req_data *data; + + data = cache_req_data_autofs_entry(mem_ctx, CACHE_REQ_AUTOFS_ENTRY_BY_NAME, + mapname, entryname); + if (data == NULL) { + return NULL; + } + + cache_req_data_set_propogate_offline_status(data, true); + + return cache_req_steal_data_and_send(mem_ctx, ev, rctx, ncache, + cache_refresh_percent, + CACHE_REQ_POSIX_DOM, domain, + data); +} diff --git a/src/responder/common/cache_req/plugins/cache_req_autofs_map_by_name.c b/src/responder/common/cache_req/plugins/cache_req_autofs_map_by_name.c new file mode 100644 index 0000000..23b11b1 --- /dev/null +++ b/src/responder/common/cache_req/plugins/cache_req_autofs_map_by_name.c @@ -0,0 +1,155 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2019 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 <talloc.h> +#include <ldb.h> + +#include "db/sysdb.h" +#include "db/sysdb_autofs.h" +#include "util/util.h" +#include "util/sss_chain_id.h" +#include "providers/data_provider.h" +#include "responder/common/cache_req/cache_req_plugin.h" + +static const char * +cache_req_autofs_map_by_name_create_debug_name(TALLOC_CTX *mem_ctx, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + return talloc_strdup(mem_ctx, data->name.name); +} + +static errno_t +cache_req_autofs_map_by_name_lookup(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result **_result) +{ + struct ldb_message *map; + struct ldb_result *result; + errno_t ret; + + ret = sysdb_get_map_byname(mem_ctx, domain, data->name.name, &map); + if (ret != EOK) { + return ret; + } + + result = cache_req_create_ldb_result_from_msg(mem_ctx, map); + if (result == NULL) { + talloc_free(map); + return ENOMEM; + } + + *_result = talloc_steal(mem_ctx, result); + return EOK; +} + +static struct tevent_req * +cache_req_autofs_map_by_name_dp_send(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result *result) +{ + struct be_conn *be_conn; + errno_t ret; + + ret = sss_dp_get_domain_conn(cr->rctx, domain->conn_name, &be_conn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "BUG: The Data Provider connection for %s is not available!\n", + domain->name); + return NULL; + } + + return sbus_call_dp_autofs_GetMap_send(mem_ctx, be_conn->conn, + be_conn->bus_name, SSS_BUS_PATH, + 0, data->name.name, + sss_chain_id_get()); +} + +bool +cache_req_autofs_map_by_name_dp_recv(struct tevent_req *subreq, + struct cache_req *cr) +{ + errno_t ret; + + ret = sbus_call_dp_autofs_GetMap_recv(subreq); + + if (ret == ERR_MISSING_DP_TARGET || ret == ENOENT) { + ret = EOK; + } + + return ret == EOK; +} + +const struct cache_req_plugin cache_req_autofs_map_by_name = { + .name = "Get autofs map", + .attr_expiration = SYSDB_CACHE_EXPIRE, + .parse_name = true, + .ignore_default_domain = true, + .bypass_cache = false, + .only_one_result = false, + .search_all_domains = false, + .require_enumeration = false, + .allow_missing_fqn = true, + .allow_switch_to_upn = false, + .upn_equivalent = CACHE_REQ_SENTINEL, + .get_next_domain_flags = 0, + + .is_well_known_fn = NULL, + .prepare_domain_data_fn = NULL, + .create_debug_name_fn = cache_req_autofs_map_by_name_create_debug_name, + .global_ncache_add_fn = NULL, + .ncache_check_fn = NULL, + .ncache_add_fn = NULL, + .ncache_filter_fn = NULL, + .lookup_fn = cache_req_autofs_map_by_name_lookup, + .dp_send_fn = cache_req_autofs_map_by_name_dp_send, + .dp_recv_fn = cache_req_autofs_map_by_name_dp_recv, + .dp_get_domain_check_fn = NULL, + .dp_get_domain_send_fn = NULL, + .dp_get_domain_recv_fn = NULL, +}; + +struct tevent_req * +cache_req_autofs_map_by_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + const char *domain, + const char *name) +{ + struct cache_req_data *data; + + data = cache_req_data_name(mem_ctx, CACHE_REQ_AUTOFS_MAP_BY_NAME, name); + if (data == NULL) { + return NULL; + } + + cache_req_data_set_propogate_offline_status(data, true); + + return cache_req_steal_data_and_send(mem_ctx, ev, rctx, ncache, + cache_refresh_percent, + CACHE_REQ_POSIX_DOM, domain, + data); +} diff --git a/src/responder/common/cache_req/plugins/cache_req_autofs_map_entries.c b/src/responder/common/cache_req/plugins/cache_req_autofs_map_entries.c new file mode 100644 index 0000000..18c08ca --- /dev/null +++ b/src/responder/common/cache_req/plugins/cache_req_autofs_map_entries.c @@ -0,0 +1,187 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2019 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 <talloc.h> +#include <ldb.h> + +#include "db/sysdb.h" +#include "db/sysdb_autofs.h" +#include "util/util.h" +#include "util/sss_chain_id.h" +#include "providers/data_provider.h" +#include "responder/common/cache_req/cache_req_plugin.h" + +static const char * +cache_req_autofs_map_entries_create_debug_name(TALLOC_CTX *mem_ctx, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + return talloc_strdup(mem_ctx, data->name.name); +} + +static errno_t +cache_req_autofs_map_entries_lookup(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result **_result) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_message *map; + struct ldb_message **mounts; + struct ldb_message **msgs; + struct ldb_result *result; + size_t count; + size_t i; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sysdb_get_map_byname(tmp_ctx, domain, data->name.name, &map); + if (ret != EOK) { + goto done; + } + + ret = sysdb_autofs_entries_by_map(tmp_ctx, domain, data->name.name, + &count, &mounts); + if (ret != EOK && ret != ENOENT) { + goto done; + } + + msgs = talloc_zero_array(tmp_ctx, struct ldb_message *, count + 1); + if (msgs == NULL) { + ret = ENOMEM; + goto done; + } + + msgs[0] = talloc_steal(msgs, map); + for (i = 0; i < count; i++) { + msgs[i + 1] = talloc_steal(msgs, mounts[i]); + } + + result = cache_req_create_ldb_result_from_msg_list(tmp_ctx, msgs, count + 1); + if (result == NULL) { + ret = ENOMEM; + goto done; + } + + *_result = talloc_steal(mem_ctx, result); + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static struct tevent_req * +cache_req_autofs_map_entries_dp_send(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result *result) +{ + struct be_conn *be_conn; + errno_t ret; + + ret = sss_dp_get_domain_conn(cr->rctx, domain->conn_name, &be_conn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "BUG: The Data Provider connection for %s is not available!\n", + domain->name); + return NULL; + } + + return sbus_call_dp_autofs_Enumerate_send(mem_ctx, be_conn->conn, + be_conn->bus_name, SSS_BUS_PATH, + 0, data->name.name, + sss_chain_id_get()); +} + +bool +cache_req_autofs_map_entries_dp_recv(struct tevent_req *subreq, + struct cache_req *cr) +{ + errno_t ret; + + ret = sbus_call_dp_autofs_Enumerate_recv(subreq); + + if (ret == ERR_MISSING_DP_TARGET || ret == ENOENT) { + ret = EOK; + } + + return ret == EOK; +} + +const struct cache_req_plugin cache_req_autofs_map_entries = { + .name = "Get autofs entries", + .attr_expiration = SYSDB_ENUM_EXPIRE, + .parse_name = true, + .ignore_default_domain = true, + .bypass_cache = false, + .only_one_result = false, + .search_all_domains = false, + .require_enumeration = false, + .allow_missing_fqn = true, + .allow_switch_to_upn = false, + .upn_equivalent = CACHE_REQ_SENTINEL, + .get_next_domain_flags = 0, + + .is_well_known_fn = NULL, + .prepare_domain_data_fn = NULL, + .create_debug_name_fn = cache_req_autofs_map_entries_create_debug_name, + .global_ncache_add_fn = NULL, + .ncache_check_fn = NULL, + .ncache_add_fn = NULL, + .ncache_filter_fn = NULL, + .lookup_fn = cache_req_autofs_map_entries_lookup, + .dp_send_fn = cache_req_autofs_map_entries_dp_send, + .dp_recv_fn = cache_req_autofs_map_entries_dp_recv, + .dp_get_domain_check_fn = NULL, + .dp_get_domain_send_fn = NULL, + .dp_get_domain_recv_fn = NULL, +}; + +struct tevent_req * +cache_req_autofs_map_entries_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + const char *domain, + const char *name) +{ + struct cache_req_data *data; + + data = cache_req_data_name(mem_ctx, CACHE_REQ_AUTOFS_MAP_ENTRIES, name); + if (data == NULL) { + return NULL; + } + + cache_req_data_set_propogate_offline_status(data, true); + + return cache_req_steal_data_and_send(mem_ctx, ev, rctx, ncache, + cache_refresh_percent, + CACHE_REQ_POSIX_DOM, domain, + data); +} diff --git a/src/responder/common/cache_req/plugins/cache_req_common.c b/src/responder/common/cache_req/plugins/cache_req_common.c new file mode 100644 index 0000000..7eb0921 --- /dev/null +++ b/src/responder/common/cache_req/plugins/cache_req_common.c @@ -0,0 +1,192 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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 <talloc.h> +#include <ldb.h> + +#include "db/sysdb.h" +#include "util/util.h" +#include "providers/data_provider.h" +#include "responder/common/cache_req/cache_req_plugin.h" + +errno_t cache_req_idminmax_check(struct cache_req_data *data, + struct sss_domain_info *domain) +{ + if (((domain->id_min != 0) && (data->id < domain->id_min)) || + ((domain->id_max != 0) && (data->id > domain->id_max))) { + DEBUG(SSSDBG_FUNC_DATA, "id exceeds min/max boundaries\n"); + return ERR_ID_OUTSIDE_RANGE; + } + return EOK; +} + +static struct ldb_message * +cache_req_well_known_sid_msg(TALLOC_CTX *mem_ctx, + const char *sid, + const char *name) +{ + struct ldb_message *msg; + const char *dup_sid; + const char *dup_name; + int ldberr; + + msg = ldb_msg_new(NULL); + if (msg == NULL) { + return NULL; + } + + dup_sid = talloc_strdup(msg, sid); + if (dup_sid == NULL) { + ldberr = LDB_ERR_OTHER; + goto done; + } + + dup_name = talloc_strdup(msg, name); + if (name == NULL) { + ldberr = LDB_ERR_OTHER; + goto done; + } + + ldberr = ldb_msg_add_string(msg, SYSDB_OBJECTCATEGORY, SYSDB_GROUP_CLASS); + if (ldberr != LDB_SUCCESS) { + goto done; + } + + ldberr = ldb_msg_add_string(msg, SYSDB_NAME, dup_name); + if (ldberr != LDB_SUCCESS) { + goto done; + } + + ldberr = ldb_msg_add_string(msg, SYSDB_SID_STR, dup_sid); + if (ldberr != LDB_SUCCESS) { + goto done; + } + +done: + if (ldberr != LDB_SUCCESS) { + talloc_free(msg); + return NULL; + } + + return msg; +} + +struct cache_req_result * +cache_req_well_known_sid_result(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + const char *domname, + const char *sid, + const char *name) +{ + struct cache_req_result *result; + struct sss_domain_info *domain; + struct ldb_message *msg; + + msg = cache_req_well_known_sid_msg(NULL, sid, name); + if (msg == NULL) { + return NULL; + } + + if (domname != NULL) { + domain = find_domain_by_name(cr->rctx->domains, domname, true); + } else { + domain = NULL; + } + + result = cache_req_create_result_from_msg(mem_ctx, domain, msg, + name, domname); + if (result == NULL) { + talloc_free(msg); + } + + return result; +} + +bool +cache_req_common_process_dp_reply(struct cache_req *cr, + errno_t ret, + uint16_t err_maj, + uint32_t err_min, + const char *err_msg) +{ + bool bret; + + if (ret != EOK) { + CACHE_REQ_DEBUG(SSSDBG_IMPORTANT_INFO, cr, + "Could not get account info [%d]: %s\n", + ret, sss_strerror(ret)); + CACHE_REQ_DEBUG(SSSDBG_TRACE_FUNC, cr, + "Due to an error we will return cached data\n"); + + bret = false; + goto done; + } + + if (err_maj) { + CACHE_REQ_DEBUG(SSSDBG_IMPORTANT_INFO, cr, + "Data Provider Error: %u, %u, %s\n", + (unsigned int)err_maj, (unsigned int)err_min, err_msg); + CACHE_REQ_DEBUG(SSSDBG_TRACE_FUNC, cr, + "Due to an error we will return cached data\n"); + + bret = false; + goto done; + } + + bret = true; + +done: + return bret; +} + +bool +cache_req_common_dp_recv(struct tevent_req *subreq, + struct cache_req *cr) +{ + const char *err_msg; + uint16_t err_maj; + uint32_t err_min; + errno_t ret; + bool bret; + + /* Use subreq as memory context so err_msg is freed with it. */ + ret = sss_dp_get_account_recv(subreq, subreq, &err_maj, &err_min, &err_msg); + bret = cache_req_common_process_dp_reply(cr, ret, err_maj, + err_min, err_msg); + + return bret; +} + +errno_t +cache_req_common_get_acct_domain_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *subreq, + struct cache_req *cr, + char **_domain) +{ + errno_t ret; + + ret = sss_dp_get_account_domain_recv(mem_ctx, subreq, _domain); + if (ret != EOK) { + CACHE_REQ_DEBUG(SSSDBG_MINOR_FAILURE, cr, + "Could not get account domain [%d]: %s\n", + ret, sss_strerror(ret)); + } + return ret; +} diff --git a/src/responder/common/cache_req/plugins/cache_req_enum_groups.c b/src/responder/common/cache_req/plugins/cache_req_enum_groups.c new file mode 100644 index 0000000..a0d1028 --- /dev/null +++ b/src/responder/common/cache_req/plugins/cache_req_enum_groups.c @@ -0,0 +1,93 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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 <talloc.h> +#include <ldb.h> + +#include "db/sysdb.h" +#include "util/util.h" +#include "providers/data_provider.h" +#include "responder/common/cache_req/cache_req_plugin.h" + +static const char * +cache_req_enum_groups_create_debug_name(TALLOC_CTX *mem_ctx, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + return talloc_strdup(mem_ctx, "Groups enumeration"); +} + +static errno_t +cache_req_enum_groups_lookup(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result **_result) +{ + return sysdb_enumgrent_with_views(mem_ctx, domain, _result); +} + +static struct tevent_req * +cache_req_enum_groups_dp_send(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result *result) +{ + return sss_dp_get_account_send(mem_ctx, cr->rctx, domain, true, + SSS_DP_GROUP, NULL, 0, NULL); +} + +static errno_t +cache_req_enum_groups_ncache_filter(struct sss_nc_ctx *ncache, + struct sss_domain_info *domain, + const char *name) +{ + return sss_ncache_check_group(ncache, domain, name); +} + +const struct cache_req_plugin cache_req_enum_groups = { + .name = "Enumerate groups", + .attr_expiration = SYSDB_CACHE_EXPIRE, + .parse_name = false, + .ignore_default_domain = false, + .bypass_cache = true, + .only_one_result = false, + .search_all_domains = true, + .require_enumeration = true, + .allow_missing_fqn = true, + .allow_switch_to_upn = false, + .upn_equivalent = CACHE_REQ_SENTINEL, + .get_next_domain_flags = SSS_GND_DESCEND, + + .is_well_known_fn = NULL, + .prepare_domain_data_fn = NULL, + .create_debug_name_fn = cache_req_enum_groups_create_debug_name, + .global_ncache_add_fn = NULL, + .ncache_check_fn = NULL, + .ncache_add_fn = NULL, + .ncache_filter_fn = cache_req_enum_groups_ncache_filter, + .lookup_fn = cache_req_enum_groups_lookup, + .dp_send_fn = cache_req_enum_groups_dp_send, + .dp_recv_fn = cache_req_common_dp_recv, + .dp_get_domain_check_fn = NULL, + .dp_get_domain_send_fn = NULL, + .dp_get_domain_recv_fn = NULL, +}; diff --git a/src/responder/common/cache_req/plugins/cache_req_enum_ip_hosts.c b/src/responder/common/cache_req/plugins/cache_req_enum_ip_hosts.c new file mode 100644 index 0000000..ae468b3 --- /dev/null +++ b/src/responder/common/cache_req/plugins/cache_req_enum_ip_hosts.c @@ -0,0 +1,126 @@ +/* + SSSD + + Authors: + Samuel Cabrero <scabrero@suse.com> + + Copyright (C) 2019 SUSE LINUX GmbH, Nuernberg, Germany. + + 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 <talloc.h> +#include <ldb.h> + +#include "db/sysdb.h" +#include "db/sysdb_iphosts.h" +#include "util/util.h" +#include "providers/data_provider.h" +#include "responder/common/cache_req/cache_req_plugin.h" + +static const char * +cache_req_enum_host_create_debug_name(TALLOC_CTX *mem_ctx, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + return talloc_strdup(mem_ctx, "IP hosts enumeration"); +} + +static errno_t +cache_req_enum_host_lookup(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result **_result) +{ + return sysdb_enumhostent(mem_ctx, domain, _result); +} + +static struct tevent_req * +cache_req_enum_host_dp_send(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result *result) +{ + return sss_dp_resolver_get_send(mem_ctx, cr->rctx, domain, true, + BE_REQ_HOST, BE_FILTER_ENUM, NULL); +} + +static bool +cache_req_enum_host_dp_recv(struct tevent_req *subreq, + struct cache_req *cr) +{ + bool bret; + uint16_t err_maj; + uint32_t err_min; + errno_t ret; + const char *err_msg; + + ret = sss_dp_resolver_get_recv(subreq, subreq, &err_maj, &err_min, + &err_msg); + bret = cache_req_common_process_dp_reply(cr, ret, err_maj, + err_min, err_msg); + + return bret; +} + +const struct cache_req_plugin cache_req_enum_ip_hosts = { + .name = "Enumerate IP hosts", + .attr_expiration = SYSDB_CACHE_EXPIRE, + .parse_name = false, + .ignore_default_domain = false, + .bypass_cache = true, + .only_one_result = false, + .search_all_domains = true, + .require_enumeration = true, + .allow_missing_fqn = true, + .allow_switch_to_upn = false, + .upn_equivalent = CACHE_REQ_SENTINEL, + .get_next_domain_flags = SSS_GND_DESCEND, + + .is_well_known_fn = NULL, + .prepare_domain_data_fn = NULL, + .create_debug_name_fn = cache_req_enum_host_create_debug_name, + .global_ncache_add_fn = NULL, + .ncache_check_fn = NULL, + .ncache_add_fn = NULL, + .ncache_filter_fn = NULL, + .lookup_fn = cache_req_enum_host_lookup, + .dp_send_fn = cache_req_enum_host_dp_send, + .dp_recv_fn = cache_req_enum_host_dp_recv, + .dp_get_domain_check_fn = NULL, + .dp_get_domain_send_fn = NULL, + .dp_get_domain_recv_fn = NULL, +}; + +struct tevent_req * +cache_req_enum_ip_hosts_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + const char *domain) +{ + struct cache_req_data *data; + + data = cache_req_data_enum(mem_ctx, CACHE_REQ_ENUM_HOST); + if (data == NULL) { + return NULL; + } + + return cache_req_steal_data_and_send(mem_ctx, ev, rctx, ncache, + cache_refresh_percent, + CACHE_REQ_POSIX_DOM, domain, data); +} diff --git a/src/responder/common/cache_req/plugins/cache_req_enum_ip_networks.c b/src/responder/common/cache_req/plugins/cache_req_enum_ip_networks.c new file mode 100644 index 0000000..e03bf6a --- /dev/null +++ b/src/responder/common/cache_req/plugins/cache_req_enum_ip_networks.c @@ -0,0 +1,126 @@ +/* + SSSD + + Authors: + Samuel Cabrero <scabrero@suse.com> + + Copyright (C) 2020 SUSE LINUX GmbH, Nuernberg, Germany. + + 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 <talloc.h> +#include <ldb.h> + +#include "db/sysdb.h" +#include "db/sysdb_ipnetworks.h" +#include "util/util.h" +#include "providers/data_provider.h" +#include "responder/common/cache_req/cache_req_plugin.h" + +static const char * +cache_req_enum_ip_networks_create_debug_name(TALLOC_CTX *mem_ctx, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + return talloc_strdup(mem_ctx, "IP networks enumeration"); +} + +static errno_t +cache_req_enum_ip_networks_lookup(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result **_result) +{ + return sysdb_enumnetent(mem_ctx, domain, _result); +} + +static struct tevent_req * +cache_req_enum_ip_networks_dp_send(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result *result) +{ + return sss_dp_resolver_get_send(mem_ctx, cr->rctx, domain, true, + BE_REQ_IP_NETWORK, BE_FILTER_ENUM, NULL); +} + +static bool +cache_req_enum_ip_networks_dp_recv(struct tevent_req *subreq, + struct cache_req *cr) +{ + bool bret; + uint16_t err_maj; + uint32_t err_min; + errno_t ret; + const char *err_msg; + + ret = sss_dp_resolver_get_recv(subreq, subreq, &err_maj, &err_min, + &err_msg); + bret = cache_req_common_process_dp_reply(cr, ret, err_maj, + err_min, err_msg); + + return bret; +} + +const struct cache_req_plugin cache_req_enum_ip_networks = { + .name = "Enumerate IP networks", + .attr_expiration = SYSDB_CACHE_EXPIRE, + .parse_name = false, + .ignore_default_domain = false, + .bypass_cache = true, + .only_one_result = false, + .search_all_domains = true, + .require_enumeration = true, + .allow_missing_fqn = true, + .allow_switch_to_upn = false, + .upn_equivalent = CACHE_REQ_SENTINEL, + .get_next_domain_flags = SSS_GND_DESCEND, + + .is_well_known_fn = NULL, + .prepare_domain_data_fn = NULL, + .create_debug_name_fn = cache_req_enum_ip_networks_create_debug_name, + .global_ncache_add_fn = NULL, + .ncache_check_fn = NULL, + .ncache_add_fn = NULL, + .ncache_filter_fn = NULL, + .lookup_fn = cache_req_enum_ip_networks_lookup, + .dp_send_fn = cache_req_enum_ip_networks_dp_send, + .dp_recv_fn = cache_req_enum_ip_networks_dp_recv, + .dp_get_domain_check_fn = NULL, + .dp_get_domain_send_fn = NULL, + .dp_get_domain_recv_fn = NULL, +}; + +struct tevent_req * +cache_req_enum_ip_networks_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + const char *domain) +{ + struct cache_req_data *data; + + data = cache_req_data_enum(mem_ctx, CACHE_REQ_ENUM_IP_NETWORK); + if (data == NULL) { + return NULL; + } + + return cache_req_steal_data_and_send(mem_ctx, ev, rctx, ncache, + cache_refresh_percent, + CACHE_REQ_POSIX_DOM, domain, data); +} diff --git a/src/responder/common/cache_req/plugins/cache_req_enum_svc.c b/src/responder/common/cache_req/plugins/cache_req_enum_svc.c new file mode 100644 index 0000000..282dc1c --- /dev/null +++ b/src/responder/common/cache_req/plugins/cache_req_enum_svc.c @@ -0,0 +1,106 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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 <talloc.h> +#include <ldb.h> + +#include "db/sysdb.h" +#include "db/sysdb_services.h" +#include "util/util.h" +#include "providers/data_provider.h" +#include "responder/common/cache_req/cache_req_plugin.h" + +static const char * +cache_req_enum_svc_create_debug_name(TALLOC_CTX *mem_ctx, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + return talloc_strdup(mem_ctx, "Services enumeration"); +} + +static errno_t +cache_req_enum_svc_lookup(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result **_result) +{ + return sysdb_enumservent(mem_ctx, domain, _result); +} + +static struct tevent_req * +cache_req_enum_svc_dp_send(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result *result) +{ + return sss_dp_get_account_send(mem_ctx, cr->rctx, domain, true, + SSS_DP_SERVICES, NULL, 0, NULL); +} + +const struct cache_req_plugin cache_req_enum_svc = { + .name = "Enumerate services", + .attr_expiration = SYSDB_CACHE_EXPIRE, + .parse_name = false, + .ignore_default_domain = false, + .bypass_cache = true, + .only_one_result = false, + .search_all_domains = true, + .require_enumeration = true, + .allow_missing_fqn = true, + .allow_switch_to_upn = false, + .upn_equivalent = CACHE_REQ_SENTINEL, + .get_next_domain_flags = SSS_GND_DESCEND, + + .is_well_known_fn = NULL, + .prepare_domain_data_fn = NULL, + .create_debug_name_fn = cache_req_enum_svc_create_debug_name, + .global_ncache_add_fn = NULL, + .ncache_check_fn = NULL, + .ncache_add_fn = NULL, + .ncache_filter_fn = NULL, + .lookup_fn = cache_req_enum_svc_lookup, + .dp_send_fn = cache_req_enum_svc_dp_send, + .dp_recv_fn = cache_req_common_dp_recv, + .dp_get_domain_check_fn = NULL, + .dp_get_domain_send_fn = NULL, + .dp_get_domain_recv_fn = NULL, +}; + +struct tevent_req * +cache_req_enum_svc_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + const char *domain) +{ + struct cache_req_data *data; + + data = cache_req_data_enum(mem_ctx, CACHE_REQ_ENUM_SVC); + if (data == NULL) { + return NULL; + } + + return cache_req_steal_data_and_send(mem_ctx, ev, rctx, ncache, + cache_refresh_percent, + CACHE_REQ_POSIX_DOM, domain, data); +} diff --git a/src/responder/common/cache_req/plugins/cache_req_enum_users.c b/src/responder/common/cache_req/plugins/cache_req_enum_users.c new file mode 100644 index 0000000..87da79f --- /dev/null +++ b/src/responder/common/cache_req/plugins/cache_req_enum_users.c @@ -0,0 +1,93 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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 <talloc.h> +#include <ldb.h> + +#include "db/sysdb.h" +#include "util/util.h" +#include "providers/data_provider.h" +#include "responder/common/cache_req/cache_req_plugin.h" + +static const char * +cache_req_enum_users_create_debug_name(TALLOC_CTX *mem_ctx, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + return talloc_strdup(mem_ctx, "Users enumeration"); +} + +static errno_t +cache_req_enum_users_lookup(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result **_result) +{ + return sysdb_enumpwent_with_views(mem_ctx, domain, _result); +} + +static struct tevent_req * +cache_req_enum_users_dp_send(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result *result) +{ + return sss_dp_get_account_send(mem_ctx, cr->rctx, domain, true, + SSS_DP_USER, NULL, 0, NULL); +} + +static errno_t +cache_req_enum_users_ncache_filter(struct sss_nc_ctx *ncache, + struct sss_domain_info *domain, + const char *name) +{ + return sss_ncache_check_user(ncache, domain, name); +} + +const struct cache_req_plugin cache_req_enum_users = { + .name = "Enumerate users", + .attr_expiration = SYSDB_CACHE_EXPIRE, + .parse_name = false, + .ignore_default_domain = false, + .bypass_cache = true, + .only_one_result = false, + .search_all_domains = true, + .require_enumeration = true, + .allow_missing_fqn = true, + .allow_switch_to_upn = false, + .upn_equivalent = CACHE_REQ_SENTINEL, + .get_next_domain_flags = SSS_GND_DESCEND, + + .is_well_known_fn = NULL, + .prepare_domain_data_fn = NULL, + .create_debug_name_fn = cache_req_enum_users_create_debug_name, + .global_ncache_add_fn = NULL, + .ncache_check_fn = NULL, + .ncache_add_fn = NULL, + .ncache_filter_fn = cache_req_enum_users_ncache_filter, + .lookup_fn = cache_req_enum_users_lookup, + .dp_send_fn = cache_req_enum_users_dp_send, + .dp_recv_fn = cache_req_common_dp_recv, + .dp_get_domain_check_fn = NULL, + .dp_get_domain_send_fn = NULL, + .dp_get_domain_recv_fn = NULL, +}; diff --git a/src/responder/common/cache_req/plugins/cache_req_group_by_filter.c b/src/responder/common/cache_req/plugins/cache_req_group_by_filter.c new file mode 100644 index 0000000..1292f18 --- /dev/null +++ b/src/responder/common/cache_req/plugins/cache_req_group_by_filter.c @@ -0,0 +1,171 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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 <talloc.h> +#include <ldb.h> + +#include "db/sysdb.h" +#include "util/util.h" +#include "providers/data_provider.h" +#include "responder/common/cache_req/cache_req_plugin.h" + +static errno_t +cache_req_group_by_filter_prepare_domain_data(struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + TALLOC_CTX *tmp_ctx; + const char *name; + errno_t ret; + + if (cr->data->name.name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: parsed name is NULL?\n"); + return ERR_INTERNAL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + name = sss_get_cased_name(tmp_ctx, cr->data->name.name, + domain->case_sensitive); + if (name == NULL) { + ret = ENOMEM; + goto done; + } + + name = sss_reverse_replace_space(tmp_ctx, name, cr->rctx->override_space); + if (name == NULL) { + ret = ENOMEM; + goto done; + } + + talloc_zfree(data->name.lookup); + data->name.lookup = talloc_steal(data, name); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static const char * +cache_req_group_by_filter_create_debug_name(TALLOC_CTX *mem_ctx, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + return talloc_strdup(mem_ctx, data->name.lookup); +} + +static errno_t +cache_req_group_by_filter_lookup(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result **_result) +{ + char *recent_filter; + errno_t ret; + + /* The "files" provider updates the record if /etc/passwd or /etc/group + * is touched. It does not perform any per-request update. + * Therefore the last update flag is not updated if no file was touched + * and we cannot use this optimization. + */ + if (is_files_provider(domain)) { + recent_filter = NULL; + } else { + recent_filter = talloc_asprintf(mem_ctx, "(%s>=%lu)", SYSDB_LAST_UPDATE, + cr->req_start); + if (recent_filter == NULL) { + return ENOMEM; + } + } + + ret = sysdb_enumgrent_filter_with_views(mem_ctx, domain, data->name.lookup, + recent_filter, _result); + talloc_free(recent_filter); + + return ret; +} + +static struct tevent_req * +cache_req_group_by_filter_dp_send(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result *result) +{ + return sss_dp_get_account_send(mem_ctx, cr->rctx, domain, true, + SSS_DP_WILDCARD_GROUP, + cr->data->name.lookup, cr->data->id, NULL); +} + +const struct cache_req_plugin cache_req_group_by_filter = { + .name = "Group by filter", + .attr_expiration = SYSDB_CACHE_EXPIRE, + .parse_name = true, + .ignore_default_domain = false, + .bypass_cache = true, + .only_one_result = false, + .search_all_domains = false, + .require_enumeration = false, + .allow_missing_fqn = false, + .allow_switch_to_upn = false, + .upn_equivalent = CACHE_REQ_SENTINEL, + .get_next_domain_flags = SSS_GND_DESCEND, + + .is_well_known_fn = NULL, + .prepare_domain_data_fn = cache_req_group_by_filter_prepare_domain_data, + .create_debug_name_fn = cache_req_group_by_filter_create_debug_name, + .global_ncache_add_fn = NULL, + .ncache_check_fn = NULL, + .ncache_add_fn = NULL, + .ncache_filter_fn = NULL, + .lookup_fn = cache_req_group_by_filter_lookup, + .dp_send_fn = cache_req_group_by_filter_dp_send, + .dp_recv_fn = cache_req_common_dp_recv, + .dp_get_domain_check_fn = NULL, + .dp_get_domain_send_fn = NULL, + .dp_get_domain_recv_fn = NULL, +}; + +struct tevent_req * +cache_req_group_by_filter_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + enum cache_req_dom_type req_dom_type, + const char *domain, + const char *filter) +{ + struct cache_req_data *data; + + data = cache_req_data_name(mem_ctx, CACHE_REQ_GROUP_BY_FILTER, filter); + if (data == NULL) { + return NULL; + } + + return cache_req_steal_data_and_send(mem_ctx, ev, rctx, NULL, + 0, + req_dom_type, domain, + data); +} diff --git a/src/responder/common/cache_req/plugins/cache_req_group_by_id.c b/src/responder/common/cache_req/plugins/cache_req_group_by_id.c new file mode 100644 index 0000000..bb72ad9 --- /dev/null +++ b/src/responder/common/cache_req/plugins/cache_req_group_by_id.c @@ -0,0 +1,246 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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 <talloc.h> +#include <ldb.h> + +#include "db/sysdb.h" +#include "util/util.h" +#include "providers/data_provider.h" +#include "responder/common/cache_req/cache_req_plugin.h" + +static const char * +cache_req_group_by_id_create_debug_name(TALLOC_CTX *mem_ctx, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + return talloc_asprintf(mem_ctx, "GID:%"PRIu32"@%s", data->id, domain->name); +} + +static errno_t +cache_req_group_by_id_ncache_check(struct sss_nc_ctx *ncache, + struct sss_domain_info *domain, + struct cache_req_data *data) +{ + errno_t ret; + + if (domain != NULL) { + ret = sss_ncache_check_gid(ncache, domain, data->id); + if (ret == EEXIST) { + return ret; + } + } + + return sss_ncache_check_gid(ncache, NULL, data->id); +} + +static errno_t +cache_req_group_by_id_ncache_filter(struct sss_nc_ctx *ncache, + struct sss_domain_info *domain, + const char *name) +{ + return sss_ncache_check_group(ncache, domain, name); +} + +static errno_t +cache_req_group_by_id_global_ncache_add(struct sss_nc_ctx *ncache, + struct cache_req_data *data) +{ + return sss_ncache_set_gid(ncache, false, NULL, data->id); +} + +static errno_t +cache_req_group_by_id_ncache_add(struct sss_nc_ctx *ncache, + struct sss_domain_info *domain, + struct cache_req_data *data) +{ + return sss_ncache_set_gid(ncache, false, domain, data->id); +} + +static errno_t +cache_req_group_by_id_lookup(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result **_result) +{ + errno_t ret; + + ret = cache_req_idminmax_check(data, domain); + if (ret != EOK) { + return ret; + } + return sysdb_getgrgid_with_views(mem_ctx, domain, data->id, _result); +} + +static errno_t +cache_req_group_by_id_dpreq_params(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct ldb_result *result, + const char **_string, + uint32_t *_id, + const char **_flag) +{ + uint32_t id; + + *_id = cr->data->id; + *_string = NULL; + *_flag = NULL; + + if (!DOM_HAS_VIEWS(cr->domain)) { + return EOK; + } + + /* We must search with views. */ + if (result == NULL || result->count == 0) { + *_flag = EXTRA_INPUT_MAYBE_WITH_VIEW; + return EOK; + } + + /* If domain has views we will try to use original values instead of the + * overridden ones. This is a must for the LOCAL view since we can't look + * it up otherwise. But it is also a shortcut for non-local views where + * we will not fail over to the overridden value. */ + + id = ldb_msg_find_attr_as_uint64(result->msgs[0], SYSDB_GIDNUM, 0); + if (id == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: id cannot be 0\n"); + *_flag = EXTRA_INPUT_MAYBE_WITH_VIEW; + return EOK; + } + + /* Now we have the original name and id. We don't have to search with + * views unless some error occurred. */ + *_id = id; + + return EOK; +} + +static struct tevent_req * +cache_req_group_by_id_dp_send(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result *result) +{ + const char *string; + const char *flag; + uint32_t id; + errno_t ret; + + ret = cache_req_group_by_id_dpreq_params(mem_ctx, cr, result, + &string, &id, &flag); + if (ret != EOK) { + return NULL; + } + + return sss_dp_get_account_send(mem_ctx, cr->rctx, domain, true, + SSS_DP_GROUP, string, id, flag); +} + +static bool +cache_req_group_by_id_get_domain_check(struct resp_ctx *rctx, + struct sss_domain_info *domain, + struct cache_req_data *data) +{ + int nret; + + nret = sss_ncache_check_locate_gid(rctx->ncache, domain, data->id); + if (nret == EEXIST) { + return false; + } + + return true; +} + +static struct tevent_req * +cache_req_group_by_id_get_domain_send(TALLOC_CTX *mem_ctx, + struct resp_ctx *rctx, + struct sss_domain_info *domain, + struct cache_req_data *data) +{ + int nret; + + nret = sss_ncache_set_locate_gid(rctx->ncache, domain, data->id); + if (nret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot set negative cache, this might result in performance degradation\n"); + /* Not fatal */ + } + + return sss_dp_get_account_domain_send(mem_ctx, + rctx, + domain, + true, /* fast_reply */ + SSS_DP_GROUP, + data->id, + NULL); +} + +const struct cache_req_plugin cache_req_group_by_id = { + .name = "Group by ID", + .attr_expiration = SYSDB_CACHE_EXPIRE, + .parse_name = false, + .ignore_default_domain = false, + .bypass_cache = false, + .only_one_result = true, + .search_all_domains = false, + .require_enumeration = false, + .allow_missing_fqn = true, + .allow_switch_to_upn = false, + .upn_equivalent = CACHE_REQ_SENTINEL, + .get_next_domain_flags = SSS_GND_DESCEND, + + .is_well_known_fn = NULL, + .prepare_domain_data_fn = NULL, + .create_debug_name_fn = cache_req_group_by_id_create_debug_name, + .global_ncache_add_fn = cache_req_group_by_id_global_ncache_add, + .ncache_check_fn = cache_req_group_by_id_ncache_check, + .ncache_add_fn = cache_req_group_by_id_ncache_add, + .ncache_filter_fn = cache_req_group_by_id_ncache_filter, + .lookup_fn = cache_req_group_by_id_lookup, + .dp_send_fn = cache_req_group_by_id_dp_send, + .dp_recv_fn = cache_req_common_dp_recv, + .dp_get_domain_check_fn = cache_req_group_by_id_get_domain_check, + .dp_get_domain_send_fn = cache_req_group_by_id_get_domain_send, + .dp_get_domain_recv_fn = cache_req_common_get_acct_domain_recv, +}; + +struct tevent_req * +cache_req_group_by_id_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + const char *domain, + gid_t gid) +{ + struct cache_req_data *data; + + data = cache_req_data_id(mem_ctx, CACHE_REQ_GROUP_BY_ID, gid); + if (data == NULL) { + return NULL; + } + + return cache_req_steal_data_and_send(mem_ctx, ev, rctx, ncache, + cache_refresh_percent, + CACHE_REQ_POSIX_DOM, domain, + data); +} diff --git a/src/responder/common/cache_req/plugins/cache_req_group_by_name.c b/src/responder/common/cache_req/plugins/cache_req_group_by_name.c new file mode 100644 index 0000000..3be0d5e --- /dev/null +++ b/src/responder/common/cache_req/plugins/cache_req_group_by_name.c @@ -0,0 +1,227 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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 <talloc.h> +#include <ldb.h> + +#include "db/sysdb.h" +#include "util/util.h" +#include "providers/data_provider.h" +#include "responder/common/cache_req/cache_req_plugin.h" + +static errno_t +cache_req_group_by_name_prepare_domain_data(struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + TALLOC_CTX *tmp_ctx; + const char *name; + errno_t ret; + + if (cr->data->name.name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: parsed name is NULL?\n"); + return ERR_INTERNAL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + name = sss_get_cased_name(tmp_ctx, cr->data->name.name, + domain->case_sensitive); + if (name == NULL) { + ret = ENOMEM; + goto done; + } + + name = sss_reverse_replace_space(tmp_ctx, name, cr->rctx->override_space); + if (name == NULL) { + ret = ENOMEM; + goto done; + } + + name = sss_create_internal_fqname(tmp_ctx, name, domain->name); + if (name == NULL) { + ret = ENOMEM; + goto done; + } + + talloc_zfree(data->name.lookup); + data->name.lookup = talloc_steal(data, name); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static const char * +cache_req_group_by_name_create_debug_name(TALLOC_CTX *mem_ctx, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + return talloc_strdup(mem_ctx, data->name.lookup); +} + +static errno_t +cache_req_group_by_name_ncache_check(struct sss_nc_ctx *ncache, + struct sss_domain_info *domain, + struct cache_req_data *data) +{ + return sss_ncache_check_group(ncache, domain, data->name.lookup); +} + +static errno_t +cache_req_group_by_name_ncache_add(struct sss_nc_ctx *ncache, + struct sss_domain_info *domain, + struct cache_req_data *data) +{ + return sss_ncache_set_group(ncache, false, domain, data->name.lookup); +} + +static errno_t +cache_req_group_by_name_lookup(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result **_result) +{ + return sysdb_getgrnam_with_views(mem_ctx, domain, data->name.lookup, + _result); +} + +static errno_t +cache_req_group_by_name_dpreq_params(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct ldb_result *result, + const char **_string, + uint32_t *_id, + const char **_flag) +{ + const char *name; + + *_id = 0; + *_string = cr->data->name.lookup; + *_flag = NULL; + + if (!DOM_HAS_VIEWS(cr->domain)) { + return EOK; + } + + /* We must search with views. */ + if (result == NULL || result->count == 0) { + *_flag = EXTRA_INPUT_MAYBE_WITH_VIEW; + return EOK; + } + + /* If domain has views we will try to use original values instead of the + * overridden ones. This is a must for the LOCAL view since we can't look + * it up otherwise. But it is also a shortcut for non-local views where + * we will not fail over to the overridden value. */ + + name = ldb_msg_find_attr_as_string(result->msgs[0], SYSDB_NAME, NULL); + if (name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: name cannot be NULL\n"); + *_flag = EXTRA_INPUT_MAYBE_WITH_VIEW; + return EOK; + } + + /* Now we have the original name and id. We don't have to search with + * views unless some error occurred. */ + *_string = talloc_steal(mem_ctx, name); + + return EOK; +} + +static struct tevent_req * +cache_req_group_by_name_dp_send(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result *result) +{ + const char *string; + const char *flag; + uint32_t id; + errno_t ret; + + ret = cache_req_group_by_name_dpreq_params(mem_ctx, cr, result, + &string, &id, &flag); + if (ret != EOK) { + return NULL; + } + + return sss_dp_get_account_send(mem_ctx, cr->rctx, domain, true, + SSS_DP_GROUP, string, id, flag); +} + +const struct cache_req_plugin cache_req_group_by_name = { + .name = "Group by name", + .attr_expiration = SYSDB_CACHE_EXPIRE, + .parse_name = true, + .ignore_default_domain = false, + .bypass_cache = false, + .only_one_result = true, + .search_all_domains = false, + .require_enumeration = false, + .allow_missing_fqn = false, + .allow_switch_to_upn = false, + .upn_equivalent = CACHE_REQ_SENTINEL, + .get_next_domain_flags = SSS_GND_DESCEND, + + .is_well_known_fn = NULL, + .prepare_domain_data_fn = cache_req_group_by_name_prepare_domain_data, + .create_debug_name_fn = cache_req_group_by_name_create_debug_name, + .global_ncache_add_fn = NULL, + .ncache_check_fn = cache_req_group_by_name_ncache_check, + .ncache_add_fn = cache_req_group_by_name_ncache_add, + .ncache_filter_fn = NULL, + .lookup_fn = cache_req_group_by_name_lookup, + .dp_send_fn = cache_req_group_by_name_dp_send, + .dp_recv_fn = cache_req_common_dp_recv, + .dp_get_domain_check_fn = NULL, + .dp_get_domain_send_fn = NULL, + .dp_get_domain_recv_fn = NULL, +}; + +struct tevent_req * +cache_req_group_by_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + enum cache_req_dom_type req_dom_type, + const char *domain, + const char *name) +{ + struct cache_req_data *data; + + data = cache_req_data_name(mem_ctx, CACHE_REQ_GROUP_BY_NAME, name); + if (data == NULL) { + return NULL; + } + + return cache_req_steal_data_and_send(mem_ctx, ev, rctx, ncache, + cache_refresh_percent, + req_dom_type, domain, + data); +} diff --git a/src/responder/common/cache_req/plugins/cache_req_initgroups_by_name.c b/src/responder/common/cache_req/plugins/cache_req_initgroups_by_name.c new file mode 100644 index 0000000..c5bea9d --- /dev/null +++ b/src/responder/common/cache_req/plugins/cache_req_initgroups_by_name.c @@ -0,0 +1,242 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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 <talloc.h> +#include <ldb.h> + +#include "db/sysdb.h" +#include "util/util.h" +#include "providers/data_provider.h" +#include "responder/common/cache_req/cache_req_plugin.h" + +static errno_t +cache_req_initgroups_by_name_prepare_domain_data(struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + TALLOC_CTX *tmp_ctx; + const char *name; + errno_t ret; + + if (cr->data->name.name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: parsed name is NULL?\n"); + return ERR_INTERNAL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + name = sss_get_cased_name(tmp_ctx, cr->data->name.name, + domain->case_sensitive); + if (name == NULL) { + ret = ENOMEM; + goto done; + } + + name = sss_reverse_replace_space(tmp_ctx, name, cr->rctx->override_space); + if (name == NULL) { + ret = ENOMEM; + goto done; + } + + name = sss_create_internal_fqname(tmp_ctx, name, domain->name); + if (name == NULL) { + ret = ENOMEM; + goto done; + } + + talloc_zfree(data->name.lookup); + data->name.lookup = talloc_steal(data, name); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static const char * +cache_req_initgroups_by_name_create_debug_name(TALLOC_CTX *mem_ctx, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + return talloc_strdup(mem_ctx, data->name.lookup); +} + +static errno_t +cache_req_initgroups_by_name_ncache_check(struct sss_nc_ctx *ncache, + struct sss_domain_info *domain, + struct cache_req_data *data) +{ + return sss_ncache_check_user(ncache, domain, data->name.lookup); +} + +static errno_t +cache_req_initgroups_by_name_ncache_add(struct sss_nc_ctx *ncache, + struct sss_domain_info *domain, + struct cache_req_data *data) +{ + return sss_ncache_set_user(ncache, false, domain, data->name.lookup); +} + +static errno_t +cache_req_initgroups_by_name_lookup(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result **_result) +{ + return sysdb_initgroups_with_views(mem_ctx, domain, data->name.lookup, + _result); +} + +static errno_t +cache_req_initgroups_by_name_dpreq_params(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct ldb_result *result, + const char **_string, + uint32_t *_id, + const char **_flag) +{ + struct ldb_result *user; + const char *name; + errno_t ret; + + *_id = 0; + *_string = cr->data->name.lookup; + *_flag = NULL; + + if (!DOM_HAS_VIEWS(cr->domain)) { + return EOK; + } + + /* We must search with views. */ + if (result == NULL || result->count == 0) { + *_flag = EXTRA_INPUT_MAYBE_WITH_VIEW; + return EOK; + } + + /* If domain has views we will try to use original values instead of the + * overridden ones. This is a must for the LOCAL view since we can't look + * it up otherwise. But it is also a shortcut for non-local views where + * we will not fail over to the overridden value. */ + + ret = sysdb_getpwnam_with_views(NULL, cr->domain, + cr->data->name.lookup, &user); + if (ret != EOK || user == NULL || user->count != 1) { + /* Case where the user is not found has been already handled. If + * this is not OK, it is an error. */ + CACHE_REQ_DEBUG(SSSDBG_CRIT_FAILURE, cr, + "Unable to match initgroups user [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + name = ldb_msg_find_attr_as_string(user->msgs[0], SYSDB_NAME, NULL); + if (name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: name cannot be NULL\n"); + talloc_free(user); + return ERR_INTERNAL; + } + + /* Now we have the original name. We don't have to search with + * views unless some error occurred. */ + *_string = talloc_steal(mem_ctx, name); + + talloc_free(user); + + return EOK; +} + +static struct tevent_req * +cache_req_initgroups_by_name_dp_send(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result *result) +{ + const char *string; + const char *flag; + uint32_t id; + errno_t ret; + + ret = cache_req_initgroups_by_name_dpreq_params(mem_ctx, cr, result, + &string, &id, &flag); + if (ret != EOK) { + return NULL; + } + + return sss_dp_get_account_send(mem_ctx, cr->rctx, domain, true, + SSS_DP_INITGROUPS, string, id, flag); +} + +const struct cache_req_plugin cache_req_initgroups_by_name = { + .name = "Initgroups by name", + .attr_expiration = SYSDB_INITGR_EXPIRE, + .parse_name = true, + .ignore_default_domain = false, + .bypass_cache = false, + .only_one_result = false, + .search_all_domains = false, + .require_enumeration = false, + .allow_missing_fqn = false, + .allow_switch_to_upn = true, + .upn_equivalent = CACHE_REQ_INITGROUPS_BY_UPN, + .get_next_domain_flags = SSS_GND_DESCEND, + + .is_well_known_fn = NULL, + .prepare_domain_data_fn = cache_req_initgroups_by_name_prepare_domain_data, + .create_debug_name_fn = cache_req_initgroups_by_name_create_debug_name, + .global_ncache_add_fn = NULL, + .ncache_check_fn = cache_req_initgroups_by_name_ncache_check, + .ncache_add_fn = cache_req_initgroups_by_name_ncache_add, + .ncache_filter_fn = NULL, + .lookup_fn = cache_req_initgroups_by_name_lookup, + .dp_send_fn = cache_req_initgroups_by_name_dp_send, + .dp_recv_fn = cache_req_common_dp_recv, + .dp_get_domain_check_fn = NULL, + .dp_get_domain_send_fn = NULL, + .dp_get_domain_recv_fn = NULL, +}; + +struct tevent_req * +cache_req_initgr_by_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + enum cache_req_dom_type req_dom_type, + const char *domain, + const char *name) +{ + struct cache_req_data *data; + + data = cache_req_data_name(mem_ctx, CACHE_REQ_INITGROUPS, name); + if (data == NULL) { + return NULL; + } + + return cache_req_steal_data_and_send(mem_ctx, ev, rctx, ncache, + cache_refresh_percent, + req_dom_type, domain, + data); +} diff --git a/src/responder/common/cache_req/plugins/cache_req_initgroups_by_upn.c b/src/responder/common/cache_req/plugins/cache_req_initgroups_by_upn.c new file mode 100644 index 0000000..9bd00f3 --- /dev/null +++ b/src/responder/common/cache_req/plugins/cache_req_initgroups_by_upn.c @@ -0,0 +1,130 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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 <talloc.h> +#include <ldb.h> + +#include "db/sysdb.h" +#include "util/util.h" +#include "providers/data_provider.h" +#include "responder/common/cache_req/cache_req_plugin.h" + +static errno_t +cache_req_initgroups_by_upn_prepare_domain_data(struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + const char *name; + + if (cr->data->name.name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: parsed UPN is NULL?\n"); + return ERR_INTERNAL; + } + + /* When looking up UPNs we don't want to reverse-replace spaces, + * just search whatever the user passed in. strdup the name so we + * can safely steal it later. + */ + name = talloc_strdup(data, cr->data->name.name); + if (name == NULL) { + return ENOMEM; + } + + talloc_zfree(data->name.lookup); + data->name.lookup = talloc_steal(data, name); + + return EOK; +} + +static const char * +cache_req_initgroups_by_upn_create_debug_name(TALLOC_CTX *mem_ctx, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + return talloc_strdup(mem_ctx, data->name.lookup); +} + +static errno_t +cache_req_initgroups_by_upn_ncache_check(struct sss_nc_ctx *ncache, + struct sss_domain_info *domain, + struct cache_req_data *data) +{ + return sss_ncache_check_upn(ncache, domain, data->name.lookup); +} + +static errno_t +cache_req_initgroups_by_upn_ncache_add(struct sss_nc_ctx *ncache, + struct sss_domain_info *domain, + struct cache_req_data *data) +{ + return sss_ncache_set_upn(ncache, false, domain, data->name.lookup); +} + +static errno_t +cache_req_initgroups_by_upn_lookup(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result **_result) +{ + return sysdb_initgroups_by_upn(mem_ctx, domain, data->name.lookup, + _result); +} + +static struct tevent_req * +cache_req_initgroups_by_upn_dp_send(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result *result) +{ + return sss_dp_get_account_send(mem_ctx, cr->rctx, domain, true, + SSS_DP_INITGROUPS, cr->data->name.lookup, + 0, EXTRA_NAME_IS_UPN); +} + +const struct cache_req_plugin cache_req_initgroups_by_upn = { + .name = "Initgroups by UPN", + .attr_expiration = SYSDB_INITGR_EXPIRE, + .parse_name = false, + .ignore_default_domain = false, + .bypass_cache = false, + .only_one_result = false, + .search_all_domains = false, + .require_enumeration = false, + .allow_missing_fqn = true, + .allow_switch_to_upn = false, + .upn_equivalent = CACHE_REQ_SENTINEL, + .get_next_domain_flags = SSS_GND_DESCEND, + + .is_well_known_fn = NULL, + .prepare_domain_data_fn = cache_req_initgroups_by_upn_prepare_domain_data, + .create_debug_name_fn = cache_req_initgroups_by_upn_create_debug_name, + .global_ncache_add_fn = NULL, + .ncache_check_fn = cache_req_initgroups_by_upn_ncache_check, + .ncache_add_fn = cache_req_initgroups_by_upn_ncache_add, + .ncache_filter_fn = NULL, + .lookup_fn = cache_req_initgroups_by_upn_lookup, + .dp_send_fn = cache_req_initgroups_by_upn_dp_send, + .dp_recv_fn = cache_req_common_dp_recv, + .dp_get_domain_check_fn = NULL, + .dp_get_domain_send_fn = NULL, + .dp_get_domain_recv_fn = NULL, +}; diff --git a/src/responder/common/cache_req/plugins/cache_req_ip_host_by_addr.c b/src/responder/common/cache_req/plugins/cache_req_ip_host_by_addr.c new file mode 100644 index 0000000..324d20e --- /dev/null +++ b/src/responder/common/cache_req/plugins/cache_req_ip_host_by_addr.c @@ -0,0 +1,174 @@ +/* + SSSD + + Authors: + Samuel Cabrero <scabrero@suse.com> + + Copyright (C) 2019 SUSE LINUX GmbH, Nuernberg, Germany. + + 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 <talloc.h> +#include <ldb.h> +#include <arpa/inet.h> + +#include "db/sysdb.h" +#include "db/sysdb_iphosts.h" +#include "util/util.h" +#include "providers/data_provider.h" +#include "responder/common/cache_req/cache_req_plugin.h" + +static errno_t +cache_req_ip_host_by_addr_prepare_domain_data(struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + char buf[INET6_ADDRSTRLEN]; + const char *addr; + + if (data->addr.len == 0 || data->addr.data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: address is NULL?\n"); + return ERR_INTERNAL; + } + + if (data->addr.af != AF_INET && data->addr.af != AF_INET6) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bad address family [%d]\n", data->addr.af); + return EAFNOSUPPORT; + } + + addr = inet_ntop(data->addr.af, data->addr.data, buf, INET6_ADDRSTRLEN); + if (addr == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to parse address: %s\n", + strerror(errno)); + return ERR_INTERNAL; + } + + talloc_zfree(data->name.lookup); + data->name.lookup = talloc_strdup(data, addr); + + return EOK; +} + +static const char * +cache_req_ip_host_by_addr_create_debug_name(TALLOC_CTX *mem_ctx, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + const char *addr = NULL; + char buf[INET6_ADDRSTRLEN]; + + addr = inet_ntop(data->addr.af, data->addr.data, buf, INET6_ADDRSTRLEN); + if (addr == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to parse network address: %s\n", + strerror(errno)); + return NULL; + } + + return talloc_strdup(mem_ctx, addr); +} + +static errno_t +cache_req_ip_host_by_addr_lookup(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result **_result) +{ + return sysdb_gethostbyaddr(mem_ctx, domain, data->name.lookup, + _result); +} + +static struct tevent_req * +cache_req_ip_host_by_addr_dp_send(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result *result) +{ + return sss_dp_resolver_get_send(mem_ctx, cr->rctx, domain, true, + BE_REQ_HOST, BE_FILTER_ADDR, + data->name.lookup); +} + +static bool +cache_req_ip_host_by_addr_dp_recv(struct tevent_req *subreq, + struct cache_req *cr) +{ + bool bret; + uint16_t err_maj; + uint32_t err_min; + errno_t ret; + const char *err_msg; + + ret = sss_dp_resolver_get_recv(subreq, subreq, &err_maj, &err_min, + &err_msg); + bret = cache_req_common_process_dp_reply(cr, ret, err_maj, + err_min, err_msg); + + return bret; +} + +const struct cache_req_plugin cache_req_ip_host_by_addr = { + .name = "IP host by address", + .attr_expiration = SYSDB_CACHE_EXPIRE, + .parse_name = true, + .ignore_default_domain = true, + .bypass_cache = false, + .only_one_result = true, + .search_all_domains = false, + .require_enumeration = false, + .allow_missing_fqn = true, + .allow_switch_to_upn = false, + .upn_equivalent = CACHE_REQ_SENTINEL, + .get_next_domain_flags = 0, + + .is_well_known_fn = NULL, + .prepare_domain_data_fn = cache_req_ip_host_by_addr_prepare_domain_data, + .create_debug_name_fn = cache_req_ip_host_by_addr_create_debug_name, + .global_ncache_add_fn = NULL, + .ncache_check_fn = NULL, + .ncache_add_fn = NULL, + .ncache_filter_fn = NULL, + .lookup_fn = cache_req_ip_host_by_addr_lookup, + .dp_send_fn = cache_req_ip_host_by_addr_dp_send, + .dp_recv_fn = cache_req_ip_host_by_addr_dp_recv, + .dp_get_domain_check_fn = NULL, + .dp_get_domain_send_fn = NULL, + .dp_get_domain_recv_fn = NULL, +}; + +struct tevent_req * +cache_req_ip_host_by_addr_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + const char *domain, + const char *name, + const char **attrs) +{ + struct cache_req_data *data; + + data = cache_req_data_name_attrs(mem_ctx, CACHE_REQ_IP_HOST_BY_NAME, + name, attrs); + if (data == NULL) { + return NULL; + } + + return cache_req_steal_data_and_send(mem_ctx, ev, rctx, ncache, + cache_refresh_percent, + CACHE_REQ_POSIX_DOM, domain, + data); +} diff --git a/src/responder/common/cache_req/plugins/cache_req_ip_host_by_name.c b/src/responder/common/cache_req/plugins/cache_req_ip_host_by_name.c new file mode 100644 index 0000000..b5f33ee --- /dev/null +++ b/src/responder/common/cache_req/plugins/cache_req_ip_host_by_name.c @@ -0,0 +1,170 @@ +/* + SSSD + + Authors: + Samuel Cabrero <scabrero@suse.com> + + Copyright (C) 2019 SUSE LINUX GmbH, Nuernberg, Germany. + + 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 <talloc.h> +#include <ldb.h> + +#include "db/sysdb.h" +#include "db/sysdb_iphosts.h" +#include "util/util.h" +#include "providers/data_provider.h" +#include "responder/common/cache_req/cache_req_plugin.h" + +static errno_t +cache_req_ip_host_by_name_prepare_domain_data(struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + TALLOC_CTX *tmp_ctx; + const char *name; + errno_t ret; + + if (data->name.name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: parsed name is NULL?\n"); + return ERR_INTERNAL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + /* Host names always case insensitive */ + name = sss_get_cased_name(tmp_ctx, data->name.name, false); + if (name == NULL) { + ret = ENOMEM; + goto done; + } + + talloc_zfree(data->name.lookup); + data->name.lookup = talloc_steal(data, name); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static const char * +cache_req_ip_host_by_name_create_debug_name(TALLOC_CTX *mem_ctx, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + const char *name = data->name.lookup; + + return talloc_asprintf(mem_ctx, "%s@%s", name, domain->name); +} + +static errno_t +cache_req_ip_host_by_name_lookup(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result **_result) +{ + return sysdb_gethostbyname(mem_ctx, domain, data->name.lookup, + _result); +} + +static struct tevent_req * +cache_req_ip_host_by_name_dp_send(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result *result) +{ + return sss_dp_resolver_get_send(mem_ctx, cr->rctx, domain, true, + BE_REQ_HOST, BE_FILTER_NAME, + data->name.lookup); +} + +static bool +cache_req_ip_host_by_name_dp_recv(struct tevent_req *subreq, + struct cache_req *cr) +{ + bool bret; + uint16_t err_maj; + uint32_t err_min; + errno_t ret; + const char *err_msg; + + ret = sss_dp_resolver_get_recv(subreq, subreq, &err_maj, &err_min, + &err_msg); + bret = cache_req_common_process_dp_reply(cr, ret, err_maj, + err_min, err_msg); + + return bret; +} + +const struct cache_req_plugin cache_req_ip_host_by_name = { + .name = "IP host by name", + .attr_expiration = SYSDB_CACHE_EXPIRE, + .parse_name = true, + .ignore_default_domain = true, + .bypass_cache = false, + .only_one_result = true, + .search_all_domains = false, + .require_enumeration = false, + .allow_missing_fqn = true, + .allow_switch_to_upn = false, + .upn_equivalent = CACHE_REQ_SENTINEL, + .get_next_domain_flags = 0, + + .is_well_known_fn = NULL, + .prepare_domain_data_fn = cache_req_ip_host_by_name_prepare_domain_data, + .create_debug_name_fn = cache_req_ip_host_by_name_create_debug_name, + .global_ncache_add_fn = NULL, + .ncache_check_fn = NULL, + .ncache_add_fn = NULL, + .ncache_filter_fn = NULL, + .lookup_fn = cache_req_ip_host_by_name_lookup, + .dp_send_fn = cache_req_ip_host_by_name_dp_send, + .dp_recv_fn = cache_req_ip_host_by_name_dp_recv, + .dp_get_domain_check_fn = NULL, + .dp_get_domain_send_fn = NULL, + .dp_get_domain_recv_fn = NULL, +}; + +struct tevent_req * +cache_req_ip_host_by_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + const char *domain, + const char *name, + const char **attrs) +{ + struct cache_req_data *data; + + data = cache_req_data_name_attrs(mem_ctx, CACHE_REQ_IP_HOST_BY_NAME, + name, attrs); + if (data == NULL) { + return NULL; + } + + return cache_req_steal_data_and_send(mem_ctx, ev, rctx, ncache, + cache_refresh_percent, + CACHE_REQ_POSIX_DOM, domain, + data); +} diff --git a/src/responder/common/cache_req/plugins/cache_req_ip_network_by_addr.c b/src/responder/common/cache_req/plugins/cache_req_ip_network_by_addr.c new file mode 100644 index 0000000..0ad7f61 --- /dev/null +++ b/src/responder/common/cache_req/plugins/cache_req_ip_network_by_addr.c @@ -0,0 +1,174 @@ +/* + SSSD + + Authors: + Samuel Cabrero <scabrero@suse.com> + + Copyright (C) 2020 SUSE LINUX GmbH, Nuernberg, Germany. + + 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 <talloc.h> +#include <ldb.h> +#include <arpa/inet.h> + +#include "db/sysdb.h" +#include "db/sysdb_ipnetworks.h" +#include "util/util.h" +#include "providers/data_provider.h" +#include "responder/common/cache_req/cache_req_plugin.h" + +static errno_t +cache_req_ip_network_by_addr_prepare_domain_data(struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + char buf[INET6_ADDRSTRLEN]; + const char *addr; + + if (data->addr.len == 0 || data->addr.data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: address is NULL?\n"); + return ERR_INTERNAL; + } + + if (data->addr.af != AF_INET && data->addr.af != AF_INET6) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bad address family [%d]\n", data->addr.af); + return EAFNOSUPPORT; + } + + addr = inet_ntop(data->addr.af, data->addr.data, buf, INET6_ADDRSTRLEN); + if (addr == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to parse address: %s\n", + strerror(errno)); + return ERR_INTERNAL; + } + + talloc_zfree(data->name.lookup); + data->name.lookup = talloc_strdup(data, addr); + + return EOK; +} + +static const char * +cache_req_ip_network_by_addr_create_debug_name(TALLOC_CTX *mem_ctx, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + const char *addr = NULL; + char buf[INET6_ADDRSTRLEN]; + + addr = inet_ntop(data->addr.af, data->addr.data, buf, INET6_ADDRSTRLEN); + if (addr == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "Failed to parse network address: %s\n", + strerror(errno)); + return NULL; + } + + return talloc_strdup(mem_ctx, addr); +} + +static errno_t +cache_req_ip_network_by_addr_lookup(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result **_result) +{ + return sysdb_getipnetworkbyaddr(mem_ctx, domain, data->name.lookup, + _result); +} + +static struct tevent_req * +cache_req_ip_network_by_addr_dp_send(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result *result) +{ + return sss_dp_resolver_get_send(mem_ctx, cr->rctx, domain, true, + BE_REQ_IP_NETWORK, BE_FILTER_ADDR, + data->name.lookup); +} + +static bool +cache_req_ip_network_by_addr_dp_recv(struct tevent_req *subreq, + struct cache_req *cr) +{ + bool bret; + uint16_t err_maj; + uint32_t err_min; + errno_t ret; + const char *err_msg; + + ret = sss_dp_resolver_get_recv(subreq, subreq, &err_maj, &err_min, + &err_msg); + bret = cache_req_common_process_dp_reply(cr, ret, err_maj, + err_min, err_msg); + + return bret; +} + +const struct cache_req_plugin cache_req_ip_network_by_addr = { + .name = "IP network by address", + .attr_expiration = SYSDB_CACHE_EXPIRE, + .parse_name = true, + .ignore_default_domain = true, + .bypass_cache = false, + .only_one_result = true, + .search_all_domains = false, + .require_enumeration = false, + .allow_missing_fqn = true, + .allow_switch_to_upn = false, + .upn_equivalent = CACHE_REQ_SENTINEL, + .get_next_domain_flags = 0, + + .is_well_known_fn = NULL, + .prepare_domain_data_fn = cache_req_ip_network_by_addr_prepare_domain_data, + .create_debug_name_fn = cache_req_ip_network_by_addr_create_debug_name, + .global_ncache_add_fn = NULL, + .ncache_check_fn = NULL, + .ncache_add_fn = NULL, + .ncache_filter_fn = NULL, + .lookup_fn = cache_req_ip_network_by_addr_lookup, + .dp_send_fn = cache_req_ip_network_by_addr_dp_send, + .dp_recv_fn = cache_req_ip_network_by_addr_dp_recv, + .dp_get_domain_check_fn = NULL, + .dp_get_domain_send_fn = NULL, + .dp_get_domain_recv_fn = NULL, +}; + +struct tevent_req * +cache_req_ip_network_by_addr_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + const char *domain, + const char *name, + const char **attrs) +{ + struct cache_req_data *data; + + data = cache_req_data_name_attrs(mem_ctx, CACHE_REQ_IP_NETWORK_BY_NAME, + name, attrs); + if (data == NULL) { + return NULL; + } + + return cache_req_steal_data_and_send(mem_ctx, ev, rctx, ncache, + cache_refresh_percent, + CACHE_REQ_POSIX_DOM, domain, + data); +} diff --git a/src/responder/common/cache_req/plugins/cache_req_ip_network_by_name.c b/src/responder/common/cache_req/plugins/cache_req_ip_network_by_name.c new file mode 100644 index 0000000..c02bc06 --- /dev/null +++ b/src/responder/common/cache_req/plugins/cache_req_ip_network_by_name.c @@ -0,0 +1,170 @@ +/* + SSSD + + Authors: + Samuel Cabrero <scabrero@suse.com> + + Copyright (C) 2020 SUSE LINUX GmbH, Nuernberg, Germany. + + 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 <talloc.h> +#include <ldb.h> + +#include "db/sysdb.h" +#include "db/sysdb_ipnetworks.h" +#include "util/util.h" +#include "providers/data_provider.h" +#include "responder/common/cache_req/cache_req_plugin.h" + +static errno_t +cache_req_ip_network_by_name_prepare_domain_data(struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + TALLOC_CTX *tmp_ctx; + const char *name; + errno_t ret; + + if (data->name.name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: parsed name is NULL?\n"); + return ERR_INTERNAL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + /* Network names always case insensitive */ + name = sss_get_cased_name(tmp_ctx, data->name.name, false); + if (name == NULL) { + ret = ENOMEM; + goto done; + } + + talloc_zfree(data->name.lookup); + data->name.lookup = talloc_steal(data, name); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static const char * +cache_req_ip_network_by_name_create_debug_name(TALLOC_CTX *mem_ctx, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + const char *name = data->name.lookup; + + return talloc_asprintf(mem_ctx, "%s@%s", name, domain->name); +} + +static errno_t +cache_req_ip_network_by_name_lookup(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result **_result) +{ + return sysdb_getipnetworkbyname(mem_ctx, domain, data->name.lookup, + _result); +} + +static struct tevent_req * +cache_req_ip_network_by_name_dp_send(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result *result) +{ + return sss_dp_resolver_get_send(mem_ctx, cr->rctx, domain, true, + BE_REQ_IP_NETWORK, BE_FILTER_NAME, + data->name.lookup); +} + +static bool +cache_req_ip_network_by_name_dp_recv(struct tevent_req *subreq, + struct cache_req *cr) +{ + bool bret; + uint16_t err_maj; + uint32_t err_min; + errno_t ret; + const char *err_msg; + + ret = sss_dp_resolver_get_recv(subreq, subreq, &err_maj, &err_min, + &err_msg); + bret = cache_req_common_process_dp_reply(cr, ret, err_maj, + err_min, err_msg); + + return bret; +} + +const struct cache_req_plugin cache_req_ip_network_by_name = { + .name = "IP network by name", + .attr_expiration = SYSDB_CACHE_EXPIRE, + .parse_name = true, + .ignore_default_domain = true, + .bypass_cache = false, + .only_one_result = true, + .search_all_domains = false, + .require_enumeration = false, + .allow_missing_fqn = true, + .allow_switch_to_upn = false, + .upn_equivalent = CACHE_REQ_SENTINEL, + .get_next_domain_flags = 0, + + .is_well_known_fn = NULL, + .prepare_domain_data_fn = cache_req_ip_network_by_name_prepare_domain_data, + .create_debug_name_fn = cache_req_ip_network_by_name_create_debug_name, + .global_ncache_add_fn = NULL, + .ncache_check_fn = NULL, + .ncache_add_fn = NULL, + .ncache_filter_fn = NULL, + .lookup_fn = cache_req_ip_network_by_name_lookup, + .dp_send_fn = cache_req_ip_network_by_name_dp_send, + .dp_recv_fn = cache_req_ip_network_by_name_dp_recv, + .dp_get_domain_check_fn = NULL, + .dp_get_domain_send_fn = NULL, + .dp_get_domain_recv_fn = NULL, +}; + +struct tevent_req * +cache_req_ip_network_by_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + const char *domain, + const char *name, + const char **attrs) +{ + struct cache_req_data *data; + + data = cache_req_data_name_attrs(mem_ctx, CACHE_REQ_IP_NETWORK_BY_NAME, + name, attrs); + if (data == NULL) { + return NULL; + } + + return cache_req_steal_data_and_send(mem_ctx, ev, rctx, ncache, + cache_refresh_percent, + CACHE_REQ_POSIX_DOM, domain, + data); +} diff --git a/src/responder/common/cache_req/plugins/cache_req_netgroup_by_name.c b/src/responder/common/cache_req/plugins/cache_req_netgroup_by_name.c new file mode 100644 index 0000000..d370d34 --- /dev/null +++ b/src/responder/common/cache_req/plugins/cache_req_netgroup_by_name.c @@ -0,0 +1,160 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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 <talloc.h> +#include <ldb.h> + +#include "db/sysdb.h" +#include "util/util.h" +#include "providers/data_provider.h" +#include "responder/common/cache_req/cache_req_plugin.h" + +static errno_t +cache_req_netgroup_by_name_prepare_domain_data(struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + TALLOC_CTX *tmp_ctx; + const char *name; + errno_t ret; + + if (data->name.name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: parsed name is NULL?\n"); + return ERR_INTERNAL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + name = sss_get_cased_name(tmp_ctx, cr->data->name.name, + domain->case_sensitive); + if (name == NULL) { + ret = ENOMEM; + goto done; + } + + talloc_zfree(data->name.lookup); + data->name.lookup = talloc_steal(data, name); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static const char * +cache_req_netgroup_by_name_create_debug_name(TALLOC_CTX *mem_ctx, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + return talloc_asprintf(mem_ctx, "%s@%s", data->name.lookup, domain->name); +} + +static errno_t +cache_req_netgroup_by_name_ncache_check(struct sss_nc_ctx *ncache, + struct sss_domain_info *domain, + struct cache_req_data *data) +{ + return sss_ncache_check_netgr(ncache, domain, data->name.lookup); +} + +static errno_t +cache_req_netgroup_by_name_ncache_add(struct sss_nc_ctx *ncache, + struct sss_domain_info *domain, + struct cache_req_data *data) +{ + return sss_ncache_set_netgr(ncache, false, domain, data->name.lookup); +} + +static errno_t +cache_req_netgroup_by_name_lookup(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result **_result) +{ + return sysdb_getnetgr(mem_ctx, domain, data->name.lookup, _result); +} + +static struct tevent_req * +cache_req_netgroup_by_name_dp_send(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result *result) +{ + return sss_dp_get_account_send(mem_ctx, cr->rctx, domain, true, + SSS_DP_NETGR, cr->data->name.lookup, + 0, NULL); +} + +const struct cache_req_plugin cache_req_netgroup_by_name = { + .name = "Netgroup by name", + .attr_expiration = SYSDB_CACHE_EXPIRE, + .parse_name = true, + .ignore_default_domain = true, + .bypass_cache = false, + .only_one_result = true, + .search_all_domains = false, + .require_enumeration = false, + .allow_missing_fqn = true, + .allow_switch_to_upn = false, + .upn_equivalent = CACHE_REQ_SENTINEL, + .get_next_domain_flags = SSS_GND_DESCEND, + + .is_well_known_fn = NULL, + .prepare_domain_data_fn = cache_req_netgroup_by_name_prepare_domain_data, + .create_debug_name_fn = cache_req_netgroup_by_name_create_debug_name, + .global_ncache_add_fn = NULL, + .ncache_check_fn = cache_req_netgroup_by_name_ncache_check, + .ncache_add_fn = cache_req_netgroup_by_name_ncache_add, + .ncache_filter_fn = NULL, + .lookup_fn = cache_req_netgroup_by_name_lookup, + .dp_send_fn = cache_req_netgroup_by_name_dp_send, + .dp_recv_fn = cache_req_common_dp_recv, + .dp_get_domain_check_fn = NULL, + .dp_get_domain_send_fn = NULL, + .dp_get_domain_recv_fn = NULL, +}; + +struct tevent_req * +cache_req_netgroup_by_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + const char *domain, + const char *name) +{ + struct cache_req_data *data; + + data = cache_req_data_name(mem_ctx, CACHE_REQ_NETGROUP_BY_NAME, name); + if (data == NULL) { + return NULL; + } + + return cache_req_steal_data_and_send(mem_ctx, ev, rctx, ncache, + cache_refresh_percent, + CACHE_REQ_POSIX_DOM, domain, + data); +} diff --git a/src/responder/common/cache_req/plugins/cache_req_object_by_id.c b/src/responder/common/cache_req/plugins/cache_req_object_by_id.c new file mode 100644 index 0000000..2bff990 --- /dev/null +++ b/src/responder/common/cache_req/plugins/cache_req_object_by_id.c @@ -0,0 +1,236 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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 <talloc.h> +#include <ldb.h> + +#include "db/sysdb.h" +#include "util/util.h" +#include "providers/data_provider.h" +#include "responder/common/cache_req/cache_req_plugin.h" + +static const char * +cache_req_object_by_id_create_debug_name(TALLOC_CTX *mem_ctx, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + return talloc_asprintf(mem_ctx, "ID:%"PRIu32"@%s", data->id, domain->name); +} + +static errno_t +cache_req_object_by_id_ncache_check(struct sss_nc_ctx *ncache, + struct sss_domain_info *domain, + struct cache_req_data *data) +{ + errno_t ret; + + ret = sss_ncache_check_uid(ncache, domain, data->id); + if (ret == EEXIST) { + ret = sss_ncache_check_gid(ncache, domain, data->id); + } + + return ret; +} + +static errno_t +cache_req_object_by_id_ncache_filter(struct sss_nc_ctx *ncache, + struct sss_domain_info *domain, + const char *name) +{ + errno_t ret; + + ret = sss_ncache_check_user(ncache, domain, name); + if (ret == EEXIST) { + ret = sss_ncache_check_group(ncache, domain, name); + } + + return ret; +} + +static errno_t +cache_req_object_by_id_global_ncache_add(struct sss_nc_ctx *ncache, + struct cache_req_data *data) +{ + errno_t ret; + + ret = sss_ncache_set_uid(ncache, false, NULL, data->id); + if (ret != EOK) { + return ret; + } + + ret = sss_ncache_set_gid(ncache, false, NULL, data->id); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +static errno_t +cache_req_object_by_id_ncache_add(struct sss_nc_ctx *ncache, + struct sss_domain_info *domain, + struct cache_req_data *data) +{ + errno_t ret; + + ret = sss_ncache_set_uid(ncache, false, domain, data->id); + if (ret != EOK) { + return ret; + } + + ret = sss_ncache_set_gid(ncache, false, domain, data->id); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +static errno_t +cache_req_object_by_id_lookup(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result **_result) +{ + errno_t ret; + + ret = cache_req_idminmax_check(data, domain); + if (ret != EOK) { + return ret; + } + return sysdb_search_object_by_id(mem_ctx, domain, data->id, + data->attrs, _result); +} + +static struct tevent_req * +cache_req_object_by_id_dp_send(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result *result) +{ + return sss_dp_get_account_send(mem_ctx, cr->rctx, domain, true, + SSS_DP_USER_AND_GROUP, NULL, + cr->data->id, NULL); +} + +static bool +cache_req_object_by_id_get_domain_check(struct resp_ctx *rctx, + struct sss_domain_info *domain, + struct cache_req_data *data) +{ + int nret; + + nret = sss_ncache_check_locate_uid(rctx->ncache, domain, data->id); + if (nret == EEXIST) { + nret = sss_ncache_check_locate_gid(rctx->ncache, domain, data->id); + if (nret == EEXIST) { + return false; + } + } + + return true; +} + +static struct tevent_req * +cache_req_object_by_id_get_domain_send(TALLOC_CTX *mem_ctx, + struct resp_ctx *rctx, + struct sss_domain_info *domain, + struct cache_req_data *data) +{ + int nret; + + nret = sss_ncache_set_locate_uid(rctx->ncache, domain, data->id); + if (nret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot set negative cache, this might result in " + "performance degradation\n"); + /* Not fatal */ + } + + nret = sss_ncache_set_locate_gid(rctx->ncache, domain, data->id); + if (nret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot set negative cache, this might result in " + "performance degradation\n"); + /* Not fatal */ + } + + return sss_dp_get_account_domain_send(mem_ctx, + rctx, + domain, + true, /* fast_reply */ + SSS_DP_USER_AND_GROUP, + data->id, + NULL); +} + +const struct cache_req_plugin cache_req_object_by_id = { + .name = "Object by ID", + .attr_expiration = SYSDB_CACHE_EXPIRE, + .parse_name = false, + .ignore_default_domain = false, + .bypass_cache = false, + .only_one_result = true, + .search_all_domains = false, + .require_enumeration = false, + .allow_missing_fqn = true, + .allow_switch_to_upn = false, + .upn_equivalent = CACHE_REQ_SENTINEL, + .get_next_domain_flags = SSS_GND_DESCEND, + + .is_well_known_fn = NULL, + .prepare_domain_data_fn = NULL, + .create_debug_name_fn = cache_req_object_by_id_create_debug_name, + .global_ncache_add_fn = cache_req_object_by_id_global_ncache_add, + .ncache_check_fn = cache_req_object_by_id_ncache_check, + .ncache_add_fn = cache_req_object_by_id_ncache_add, + .ncache_filter_fn = cache_req_object_by_id_ncache_filter, + .lookup_fn = cache_req_object_by_id_lookup, + .dp_send_fn = cache_req_object_by_id_dp_send, + .dp_recv_fn = cache_req_common_dp_recv, + .dp_get_domain_check_fn = cache_req_object_by_id_get_domain_check, + .dp_get_domain_send_fn = cache_req_object_by_id_get_domain_send, + .dp_get_domain_recv_fn = cache_req_common_get_acct_domain_recv, +}; + +struct tevent_req * +cache_req_object_by_id_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + const char *domain, + uint32_t id, + const char **attrs) +{ + struct cache_req_data *data; + + data = cache_req_data_id_attrs(mem_ctx, CACHE_REQ_OBJECT_BY_ID, id, attrs); + if (data == NULL) { + return NULL; + } + + return cache_req_steal_data_and_send(mem_ctx, ev, rctx, ncache, + cache_refresh_percent, + CACHE_REQ_POSIX_DOM, domain, + data); +} diff --git a/src/responder/common/cache_req/plugins/cache_req_object_by_name.c b/src/responder/common/cache_req/plugins/cache_req_object_by_name.c new file mode 100644 index 0000000..907f1f3 --- /dev/null +++ b/src/responder/common/cache_req/plugins/cache_req_object_by_name.c @@ -0,0 +1,238 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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 <talloc.h> +#include <ldb.h> + +#include "db/sysdb.h" +#include "util/util.h" +#include "providers/data_provider.h" +#include "responder/common/cache_req/cache_req_plugin.h" + +static errno_t +cache_req_object_by_name_well_known(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct cache_req_result **_result) +{ + struct cache_req_result *result; + const char *sid; + char *domname; + char *name; + errno_t ret; + + ret = sss_parse_name(mem_ctx, cr->rctx->global_names, + data->name.input, &domname, &name); + if (ret != EOK) { + CACHE_REQ_DEBUG(SSSDBG_OP_FAILURE, cr, "Unable to parse name " + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + + if (domname == NULL || name == NULL) { + CACHE_REQ_DEBUG(SSSDBG_FUNC_DATA, cr, "Unable to split [%s] in " + "name and domain part. Skipping detection of " + "well-known name.\n", data->name.input); + return ENOENT; + } + + ret = name_to_well_known_sid(domname, name, &sid); + if (ret != EOK) { + return ret; + } + + result = cache_req_well_known_sid_result(mem_ctx, cr, domname, sid, name); + talloc_free(domname); + talloc_free(name); + if (result == NULL) { + return ENOMEM; + } + + *_result = result; + + return EOK; +} + +static errno_t +cache_req_object_by_name_prepare_domain_data(struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + TALLOC_CTX *tmp_ctx; + const char *name; + errno_t ret; + + if (cr->data->name.name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: parsed name is NULL?\n"); + return ERR_INTERNAL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + name = sss_get_cased_name(tmp_ctx, cr->data->name.name, + domain->case_sensitive); + if (name == NULL) { + ret = ENOMEM; + goto done; + } + + name = sss_reverse_replace_space(tmp_ctx, name, cr->rctx->override_space); + if (name == NULL) { + ret = ENOMEM; + goto done; + } + + name = sss_create_internal_fqname(tmp_ctx, name, domain->name); + if (name == NULL) { + ret = ENOMEM; + goto done; + } + + talloc_zfree(data->name.lookup); + data->name.lookup = talloc_steal(data, name); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static const char * +cache_req_object_by_name_create_debug_name(TALLOC_CTX *mem_ctx, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + return talloc_strdup(mem_ctx, data->name.lookup); +} + +static errno_t +cache_req_object_by_name_ncache_check(struct sss_nc_ctx *ncache, + struct sss_domain_info *domain, + struct cache_req_data *data) +{ + errno_t ret; + + ret = sss_ncache_check_user(ncache, domain, data->name.lookup); + if (ret == EEXIST) { + ret = sss_ncache_check_group(ncache, domain, data->name.lookup); + } + + return ret; +} + +static errno_t +cache_req_object_by_name_ncache_add(struct sss_nc_ctx *ncache, + struct sss_domain_info *domain, + struct cache_req_data *data) +{ + errno_t ret; + + ret = sss_ncache_set_user(ncache, false, domain, data->name.lookup); + if (ret != EOK) { + return ret; + } + + ret = sss_ncache_set_group(ncache, false, domain, data->name.lookup); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +static errno_t +cache_req_object_by_name_lookup(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result **_result) +{ + return sysdb_search_object_by_name(mem_ctx, domain, data->name.lookup, + data->attrs, _result); +} + +static struct tevent_req * +cache_req_object_by_name_dp_send(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result *result) +{ + return sss_dp_get_account_send(mem_ctx, cr->rctx, domain, true, + SSS_DP_USER_AND_GROUP, + cr->data->name.lookup, 0, NULL); +} + +const struct cache_req_plugin cache_req_object_by_name = { + .name = "Object by name", + .attr_expiration = SYSDB_CACHE_EXPIRE, + .parse_name = true, + .ignore_default_domain = false, + .bypass_cache = false, + .only_one_result = false, + .search_all_domains = false, + .require_enumeration = false, + .allow_missing_fqn = false, + .allow_switch_to_upn = true, + .upn_equivalent = CACHE_REQ_USER_BY_UPN, + .get_next_domain_flags = SSS_GND_DESCEND, + + .is_well_known_fn = cache_req_object_by_name_well_known, + .prepare_domain_data_fn = cache_req_object_by_name_prepare_domain_data, + .create_debug_name_fn = cache_req_object_by_name_create_debug_name, + .global_ncache_add_fn = NULL, + .ncache_check_fn = cache_req_object_by_name_ncache_check, + .ncache_add_fn = cache_req_object_by_name_ncache_add, + .ncache_filter_fn = NULL, + .lookup_fn = cache_req_object_by_name_lookup, + .dp_send_fn = cache_req_object_by_name_dp_send, + .dp_recv_fn = cache_req_common_dp_recv, + .dp_get_domain_check_fn = NULL, + .dp_get_domain_send_fn = NULL, + .dp_get_domain_recv_fn = NULL, +}; + +struct tevent_req * +cache_req_object_by_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + const char *domain, + const char *name, + const char **attrs) +{ + struct cache_req_data *data; + + data = cache_req_data_name_attrs(mem_ctx, CACHE_REQ_OBJECT_BY_NAME, + name, attrs); + if (data == NULL) { + return NULL; + } + + return cache_req_steal_data_and_send(mem_ctx, ev, rctx, ncache, + cache_refresh_percent, + CACHE_REQ_POSIX_DOM, domain, + data); +} diff --git a/src/responder/common/cache_req/plugins/cache_req_object_by_sid.c b/src/responder/common/cache_req/plugins/cache_req_object_by_sid.c new file mode 100644 index 0000000..db731b1 --- /dev/null +++ b/src/responder/common/cache_req/plugins/cache_req_object_by_sid.c @@ -0,0 +1,202 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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 <talloc.h> +#include <ldb.h> + +#include "db/sysdb.h" +#include "util/util.h" +#include "providers/data_provider.h" +#include "responder/common/cache_req/cache_req_plugin.h" + +static errno_t +cache_req_object_by_sid_well_known(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct cache_req_result **_result) +{ + struct cache_req_result *result; + const char *domname; + const char *name; + errno_t ret; + + ret = well_known_sid_to_name(data->sid, &domname, &name); + if (ret != EOK) { + CACHE_REQ_DEBUG(SSSDBG_TRACE_ALL, cr, + "SID [%s] is not a Well-Known SID.\n", data->sid); + return ret; + } + + result = cache_req_well_known_sid_result(mem_ctx, cr, domname, + data->sid, name); + if (result == NULL) { + return ENOMEM; + } + + *_result = result; + + return EOK; +} + +static const char * +cache_req_object_by_sid_create_debug_name(TALLOC_CTX *mem_ctx, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + return talloc_asprintf(mem_ctx, "SID:%s@%s", data->sid, domain->name); +} + +static errno_t +cache_req_object_by_sid_ncache_check(struct sss_nc_ctx *ncache, + struct sss_domain_info *domain, + struct cache_req_data *data) +{ + return sss_ncache_check_sid(ncache, domain, data->sid); +} + +static errno_t +cache_req_object_by_sid_global_ncache_add(struct sss_nc_ctx *ncache, + struct cache_req_data *data) +{ + return sss_ncache_set_sid(ncache, false, NULL, data->sid); +} + +static errno_t +cache_req_object_by_sid_ncache_add(struct sss_nc_ctx *ncache, + struct sss_domain_info *domain, + struct cache_req_data *data) +{ + return sss_ncache_set_sid(ncache, false, domain, data->sid); +} + +static errno_t +cache_req_object_by_sid_lookup(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result **_result) +{ + return sysdb_search_object_by_sid(mem_ctx, domain, data->sid, data->attrs, + _result); +} + +static struct tevent_req * +cache_req_object_by_sid_dp_send(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result *result) +{ + return sss_dp_get_account_send(mem_ctx, cr->rctx, domain, true, + SSS_DP_SECID, cr->data->sid, 0, NULL); +} + +static bool +cache_req_object_by_sid_get_domain_check(struct resp_ctx *rctx, + struct sss_domain_info *domain, + struct cache_req_data *data) +{ + int nret; + + nret = sss_ncache_check_locate_sid(rctx->ncache, domain, data->sid); + if (nret == EEXIST) { + return false; + } + + return true; +} + +static struct tevent_req * +cache_req_object_by_sid_get_domain_send(TALLOC_CTX *mem_ctx, + struct resp_ctx *rctx, + struct sss_domain_info *domain, + struct cache_req_data *data) +{ + int nret; + + nret = sss_ncache_set_locate_sid(rctx->ncache, domain, data->sid); + if (nret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot set negative cache, this might result in " + "performance degradation\n"); + /* Not fatal */ + } + + return sss_dp_get_account_domain_send(mem_ctx, + rctx, + domain, + true, /* fast_reply */ + SSS_DP_SECID, + 0, + data->sid); +} + + +const struct cache_req_plugin cache_req_object_by_sid = { + .name = "Object by SID", + .attr_expiration = SYSDB_CACHE_EXPIRE, + .parse_name = false, + .ignore_default_domain = false, + .bypass_cache = false, + .only_one_result = true, + .search_all_domains = false, + .require_enumeration = false, + .allow_missing_fqn = true, + .allow_switch_to_upn = false, + .upn_equivalent = CACHE_REQ_SENTINEL, + .get_next_domain_flags = SSS_GND_DESCEND, + + .is_well_known_fn = cache_req_object_by_sid_well_known, + .prepare_domain_data_fn = NULL, + .create_debug_name_fn = cache_req_object_by_sid_create_debug_name, + .global_ncache_add_fn = cache_req_object_by_sid_global_ncache_add, + .ncache_check_fn = cache_req_object_by_sid_ncache_check, + .ncache_add_fn = cache_req_object_by_sid_ncache_add, + .ncache_filter_fn = NULL, + .lookup_fn = cache_req_object_by_sid_lookup, + .dp_send_fn = cache_req_object_by_sid_dp_send, + .dp_recv_fn = cache_req_common_dp_recv, + .dp_get_domain_check_fn = cache_req_object_by_sid_get_domain_check, + .dp_get_domain_send_fn = cache_req_object_by_sid_get_domain_send, + .dp_get_domain_recv_fn = cache_req_common_get_acct_domain_recv, +}; + +struct tevent_req * +cache_req_object_by_sid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + const char *domain, + const char *sid, + const char **attrs) +{ + struct cache_req_data *data; + + data = cache_req_data_sid(mem_ctx, CACHE_REQ_OBJECT_BY_SID, sid, attrs); + if (data == NULL) { + return NULL; + } + + return cache_req_steal_data_and_send(mem_ctx, ev, rctx, ncache, + cache_refresh_percent, + CACHE_REQ_POSIX_DOM, domain, + data); +} diff --git a/src/responder/common/cache_req/plugins/cache_req_ssh_host_id_by_name.c b/src/responder/common/cache_req/plugins/cache_req_ssh_host_id_by_name.c new file mode 100644 index 0000000..29f52f1 --- /dev/null +++ b/src/responder/common/cache_req/plugins/cache_req_ssh_host_id_by_name.c @@ -0,0 +1,164 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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 <talloc.h> +#include <ldb.h> + +#include "db/sysdb_ssh.h" +#include "util/util.h" +#include "util/sss_chain_id.h" +#include "providers/data_provider.h" +#include "responder/common/cache_req/cache_req_plugin.h" + +static const char * +cache_req_host_by_name_create_debug_name(TALLOC_CTX *mem_ctx, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + return talloc_strdup(mem_ctx, data->name.name); +} + +static errno_t +cache_req_host_by_name_lookup(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result **_result) +{ +#ifdef BUILD_SSH + struct ldb_result *result; + struct ldb_message *msg; + errno_t ret; + + ret = sysdb_get_ssh_host(mem_ctx, domain, data->name.name, + data->attrs, &msg); + if (ret != EOK) { + return ret; + } + + result = cache_req_create_ldb_result_from_msg(mem_ctx, msg); + if (result == NULL) { + return ENOMEM; + } + + *_result = result; + + return EOK; +#else + return ERR_INTERNAL; +#endif /* BUILD_SSH */ +} + +static struct tevent_req * +cache_req_host_by_name_dp_send(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result *result) +{ + struct be_conn *be_conn; + errno_t ret; + + ret = sss_dp_get_domain_conn(cr->rctx, domain->conn_name, &be_conn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "BUG: The Data Provider connection for %s is not available!\n", + domain->name); + return NULL; + } + + return sbus_call_dp_dp_hostHandler_send(mem_ctx, be_conn->conn, + be_conn->bus_name, SSS_BUS_PATH, + 0, data->name.name, data->alias, + sss_chain_id_get()); +} + +static bool +cache_req_host_by_name_dp_recv(struct tevent_req *subreq, + struct cache_req *cr) +{ + const char *err_msg; + dbus_uint16_t err_maj; + dbus_uint32_t err_min; + errno_t ret; + bool bret; + + /* Use subreq as memory context so err_msg is freed with it. */ + ret = sbus_call_dp_dp_hostHandler_recv(subreq, subreq, &err_maj, + &err_min, &err_msg); + bret = cache_req_common_process_dp_reply(cr, ret, err_maj, + err_min, err_msg); + + return bret; +} + +const struct cache_req_plugin cache_req_ssh_host_id_by_name = { + .name = "SSH Host ID by name", + .attr_expiration = SYSDB_CACHE_EXPIRE, + .parse_name = true, + .ignore_default_domain = true, + .bypass_cache = true, + .only_one_result = true, + .search_all_domains = false, + .require_enumeration = false, + .allow_missing_fqn = true, + .allow_switch_to_upn = false, + .upn_equivalent = CACHE_REQ_SENTINEL, + .get_next_domain_flags = 0, + + .is_well_known_fn = NULL, + .prepare_domain_data_fn = NULL, + .create_debug_name_fn = cache_req_host_by_name_create_debug_name, + .global_ncache_add_fn = NULL, + .ncache_check_fn = NULL, + .ncache_add_fn = NULL, + .ncache_filter_fn = NULL, + .lookup_fn = cache_req_host_by_name_lookup, + .dp_send_fn = cache_req_host_by_name_dp_send, + .dp_recv_fn = cache_req_host_by_name_dp_recv, + .dp_get_domain_check_fn = NULL, + .dp_get_domain_send_fn = NULL, + .dp_get_domain_recv_fn = NULL, +}; + +struct tevent_req * +cache_req_ssh_host_id_by_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + const char *domain, + const char *name, + const char *alias, + const char **attrs) +{ + struct cache_req_data *data; + + data = cache_req_data_ssh_host_id(mem_ctx, CACHE_REQ_SSH_HOST_ID_BY_NAME, + name, alias, attrs); + if (data == NULL) { + return NULL; + } + + return cache_req_steal_data_and_send(mem_ctx, ev, rctx, ncache, + cache_refresh_percent, + CACHE_REQ_POSIX_DOM, domain, + data); +} diff --git a/src/responder/common/cache_req/plugins/cache_req_subid_ranges_by_name.c b/src/responder/common/cache_req/plugins/cache_req_subid_ranges_by_name.c new file mode 100644 index 0000000..5485271 --- /dev/null +++ b/src/responder/common/cache_req/plugins/cache_req_subid_ranges_by_name.c @@ -0,0 +1,143 @@ +/* + Copyright (C) 2021 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 <talloc.h> +#include <ldb.h> + +#include "db/sysdb.h" +#include "db/sysdb_subid.h" +#include "util/util.h" +#include "providers/data_provider.h" +#include "responder/common/cache_req/cache_req_plugin.h" + +static errno_t +cache_req_subid_ranges_by_name_prepare_domain_data(struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + TALLOC_CTX *tmp_ctx; + const char *name; + errno_t ret; + + if (cr->data->name.name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: parsed name is NULL?\n"); + return ERR_INTERNAL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + name = sss_get_cased_name(tmp_ctx, cr->data->name.name, + domain->case_sensitive); + if (name == NULL) { + ret = ENOMEM; + goto done; + } + + name = sss_reverse_replace_space(tmp_ctx, name, cr->rctx->override_space); + if (name == NULL) { + ret = ENOMEM; + goto done; + } + + talloc_zfree(data->name.lookup); + data->name.lookup = talloc_steal(data, name); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static const char * +cache_req_subid_ranges_by_name_create_debug_name(TALLOC_CTX *mem_ctx, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + return talloc_strdup(mem_ctx, data->name.lookup); +} + +static errno_t +cache_req_subid_ranges_by_name_lookup(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result **_result) +{ + struct ldb_result *result; + struct ldb_message *msg; + errno_t ret; + + ret = sysdb_get_subid_ranges(mem_ctx, domain, data->name.name, + data->attrs, &msg); + if (ret != EOK) { + return ret; + } + + result = cache_req_create_ldb_result_from_msg(mem_ctx, msg); + if (result == NULL) { + return ENOMEM; + } + + *_result = result; + + return EOK; +} + +static struct tevent_req * +cache_req_subid_ranges_by_name_dp_send(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result *result) +{ + /* Views aren't yet supported */ + return sss_dp_get_account_send(mem_ctx, cr->rctx, domain, true, + SSS_DP_SUBID_RANGES, cr->data->name.lookup, 0, NULL); +} + +const struct cache_req_plugin cache_req_subid_ranges_by_name = { + .name = "SubID ranges by name", + .attr_expiration = SYSDB_CACHE_EXPIRE, + .parse_name = true, + .ignore_default_domain = false, + .bypass_cache = false, + .only_one_result = false, + .search_all_domains = false, + .require_enumeration = false, + .allow_missing_fqn = false, + .allow_switch_to_upn = false, + .upn_equivalent = CACHE_REQ_SENTINEL, + .get_next_domain_flags = SSS_GND_DESCEND, + + .is_well_known_fn = NULL, + .prepare_domain_data_fn = cache_req_subid_ranges_by_name_prepare_domain_data, + .create_debug_name_fn = cache_req_subid_ranges_by_name_create_debug_name, + .global_ncache_add_fn = NULL, + .ncache_check_fn = NULL, + .ncache_add_fn = NULL, + .ncache_filter_fn = NULL, + .lookup_fn = cache_req_subid_ranges_by_name_lookup, + .dp_send_fn = cache_req_subid_ranges_by_name_dp_send, + .dp_recv_fn = cache_req_common_dp_recv, + .dp_get_domain_check_fn = NULL, + .dp_get_domain_send_fn = NULL, + .dp_get_domain_recv_fn = NULL, +}; diff --git a/src/responder/common/cache_req/plugins/cache_req_svc_by_name.c b/src/responder/common/cache_req/plugins/cache_req_svc_by_name.c new file mode 100644 index 0000000..5b17051 --- /dev/null +++ b/src/responder/common/cache_req/plugins/cache_req_svc_by_name.c @@ -0,0 +1,185 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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 <talloc.h> +#include <ldb.h> + +#include "db/sysdb.h" +#include "db/sysdb_services.h" +#include "util/util.h" +#include "providers/data_provider.h" +#include "responder/common/cache_req/cache_req_plugin.h" + +static errno_t +cache_req_svc_by_name_prepare_domain_data(struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + TALLOC_CTX *tmp_ctx; + const char *name; + const char *protocol; + errno_t ret; + + if (data->svc.name->name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: parsed name is NULL?\n"); + return ERR_INTERNAL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + name = sss_get_cased_name(tmp_ctx, data->svc.name->name, + domain->case_sensitive); + if (name == NULL) { + ret = ENOMEM; + goto done; + } + + if (data->svc.protocol.name == NULL) { + protocol = NULL; + } else { + protocol = sss_get_cased_name(tmp_ctx, data->svc.protocol.name, + domain->case_sensitive); + if (protocol == NULL) { + ret = ENOMEM; + goto done; + } + } + + talloc_zfree(data->svc.name->lookup); + talloc_zfree(data->svc.protocol.lookup); + data->svc.name->lookup = talloc_steal(data, name); + data->svc.protocol.lookup = talloc_steal(data, protocol); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static const char * +cache_req_svc_by_name_create_debug_name(TALLOC_CTX *mem_ctx, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + const char *protocol = data->svc.protocol.lookup; + const char *name = data->svc.name->lookup; + + protocol = protocol == NULL ? "<ANY>" : protocol; + + return talloc_asprintf(mem_ctx, "%s %s@%s", protocol, name, domain->name); +} + +static errno_t +cache_req_svc_by_name_ncache_check(struct sss_nc_ctx *ncache, + struct sss_domain_info *domain, + struct cache_req_data *data) +{ + return sss_ncache_check_service(ncache, domain, data->svc.name->lookup, + data->svc.protocol.lookup); +} + +static errno_t +cache_req_svc_by_name_ncache_add(struct sss_nc_ctx *ncache, + struct sss_domain_info *domain, + struct cache_req_data *data) +{ + return sss_ncache_set_service_name(ncache, false, domain, + data->svc.name->lookup, + data->svc.protocol.lookup); +} + +static errno_t +cache_req_svc_by_name_lookup(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result **_result) +{ + return sysdb_getservbyname(mem_ctx, domain, data->svc.name->lookup, + data->svc.protocol.lookup, _result); +} + +static struct tevent_req * +cache_req_svc_by_name_dp_send(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result *result) +{ + return sss_dp_get_account_send(mem_ctx, cr->rctx, domain, true, + SSS_DP_SERVICES, cr->data->svc.name->lookup, + 0, cr->data->svc.protocol.lookup); +} + +const struct cache_req_plugin cache_req_svc_by_name = { + .name = "Service by name", + .attr_expiration = SYSDB_CACHE_EXPIRE, + .parse_name = true, + .ignore_default_domain = false, + .bypass_cache = false, + .only_one_result = false, + .search_all_domains = false, + .require_enumeration = false, + .allow_missing_fqn = false, + .allow_switch_to_upn = false, + .upn_equivalent = CACHE_REQ_SENTINEL, + .get_next_domain_flags = SSS_GND_DESCEND, + + .is_well_known_fn = NULL, + .prepare_domain_data_fn = cache_req_svc_by_name_prepare_domain_data, + .create_debug_name_fn = cache_req_svc_by_name_create_debug_name, + .global_ncache_add_fn = NULL, + .ncache_check_fn = cache_req_svc_by_name_ncache_check, + .ncache_add_fn = cache_req_svc_by_name_ncache_add, + .ncache_filter_fn = NULL, + .lookup_fn = cache_req_svc_by_name_lookup, + .dp_send_fn = cache_req_svc_by_name_dp_send, + .dp_recv_fn = cache_req_common_dp_recv, + .dp_get_domain_check_fn = NULL, + .dp_get_domain_send_fn = NULL, + .dp_get_domain_recv_fn = NULL, +}; + +struct tevent_req * +cache_req_svc_by_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + const char *domain, + const char *name, + const char *protocol) +{ + struct cache_req_data *data; + + data = cache_req_data_svc(mem_ctx, CACHE_REQ_SVC_BY_NAME, name, protocol, 0); + if (data == NULL) { + return NULL; + } + + return cache_req_steal_data_and_send(mem_ctx, ev, rctx, ncache, + cache_refresh_percent, + CACHE_REQ_POSIX_DOM, domain, + data); +} diff --git a/src/responder/common/cache_req/plugins/cache_req_svc_by_port.c b/src/responder/common/cache_req/plugins/cache_req_svc_by_port.c new file mode 100644 index 0000000..4c005df --- /dev/null +++ b/src/responder/common/cache_req/plugins/cache_req_svc_by_port.c @@ -0,0 +1,159 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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 <talloc.h> +#include <ldb.h> + +#include "db/sysdb.h" +#include "db/sysdb_services.h" +#include "util/util.h" +#include "providers/data_provider.h" +#include "responder/common/cache_req/cache_req_plugin.h" + +static errno_t +cache_req_svc_by_port_prepare_domain_data(struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + const char *protocol; + + if (data->svc.protocol.name == NULL) { + return EOK; + } + + protocol = sss_get_cased_name(NULL, data->svc.protocol.name, + domain->case_sensitive); + if (protocol == NULL) { + return ENOMEM; + } + + talloc_zfree(data->svc.protocol.lookup); + data->svc.protocol.lookup = talloc_steal(data, protocol); + + return EOK; +} + +static const char * +cache_req_svc_by_port_create_debug_name(TALLOC_CTX *mem_ctx, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + const char *protocol = data->svc.protocol.lookup; + + protocol = protocol == NULL ? "<ANY>" : protocol; + + return talloc_asprintf(mem_ctx, "%s %u@%s", protocol, + data->svc.port, domain->name); +} + +static errno_t +cache_req_svc_by_port_ncache_check(struct sss_nc_ctx *ncache, + struct sss_domain_info *domain, + struct cache_req_data *data) +{ + return sss_ncache_check_service_port(ncache, domain, data->svc.port, + data->svc.protocol.lookup); +} + +static errno_t +cache_req_svc_by_port_ncache_add(struct sss_nc_ctx *ncache, + struct sss_domain_info *domain, + struct cache_req_data *data) +{ + return sss_ncache_set_service_port(ncache, false, domain, + data->svc.port, + data->svc.protocol.lookup); +} + +static errno_t +cache_req_svc_by_port_lookup(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result **_result) +{ + return sysdb_getservbyport(mem_ctx, domain, data->svc.port, + data->svc.protocol.lookup, _result); +} + +static struct tevent_req * +cache_req_svc_by_port_dp_send(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result *result) +{ + return sss_dp_get_account_send(mem_ctx, cr->rctx, domain, true, + SSS_DP_SERVICES, NULL, cr->data->svc.port, + cr->data->svc.protocol.lookup); +} + +const struct cache_req_plugin cache_req_svc_by_port = { + .name = "Service by port", + .attr_expiration = SYSDB_CACHE_EXPIRE, + .parse_name = false, + .ignore_default_domain = false, + .bypass_cache = false, + .only_one_result = false, + .search_all_domains = false, + .require_enumeration = false, + .allow_missing_fqn = false, + .allow_switch_to_upn = false, + .upn_equivalent = CACHE_REQ_SENTINEL, + .get_next_domain_flags = SSS_GND_DESCEND, + + .is_well_known_fn = NULL, + .prepare_domain_data_fn = cache_req_svc_by_port_prepare_domain_data, + .create_debug_name_fn = cache_req_svc_by_port_create_debug_name, + .global_ncache_add_fn = NULL, + .ncache_check_fn = cache_req_svc_by_port_ncache_check, + .ncache_add_fn = cache_req_svc_by_port_ncache_add, + .ncache_filter_fn = NULL, + .lookup_fn = cache_req_svc_by_port_lookup, + .dp_send_fn = cache_req_svc_by_port_dp_send, + .dp_recv_fn = cache_req_common_dp_recv, + .dp_get_domain_check_fn = NULL, + .dp_get_domain_send_fn = NULL, + .dp_get_domain_recv_fn = NULL, +}; + +struct tevent_req * +cache_req_svc_by_port_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + const char *domain, + uint16_t port, + const char *protocol) +{ + struct cache_req_data *data; + + data = cache_req_data_svc(mem_ctx, CACHE_REQ_SVC_BY_PORT, + NULL, protocol, port); + if (data == NULL) { + return NULL; + } + + return cache_req_steal_data_and_send(mem_ctx, ev, rctx, ncache, + cache_refresh_percent, + CACHE_REQ_POSIX_DOM, domain, + data); +} diff --git a/src/responder/common/cache_req/plugins/cache_req_user_by_cert.c b/src/responder/common/cache_req/plugins/cache_req_user_by_cert.c new file mode 100644 index 0000000..a2dc1fa --- /dev/null +++ b/src/responder/common/cache_req/plugins/cache_req_user_by_cert.c @@ -0,0 +1,127 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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 <talloc.h> +#include <ldb.h> + +#include "db/sysdb.h" +#include "util/util.h" +#include "providers/data_provider.h" +#include "responder/common/cache_req/cache_req_plugin.h" + +static const char * +cache_req_user_by_cert_create_debug_name(TALLOC_CTX *mem_ctx, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + /* Certificates might be quite long, thus we only use + * the last 10 characters for logging. */ + return talloc_asprintf(mem_ctx, "CERT:%s@%s", + get_last_x_chars(data->cert, 10), domain->name); +} + +static errno_t +cache_req_user_by_cert_ncache_check(struct sss_nc_ctx *ncache, + struct sss_domain_info *domain, + struct cache_req_data *data) +{ + return sss_ncache_check_cert(ncache, data->cert); +} + +static errno_t +cache_req_user_by_cert_global_ncache_add(struct sss_nc_ctx *ncache, + struct cache_req_data *data) +{ + return sss_ncache_set_cert(ncache, false, data->cert); +} + +static errno_t +cache_req_user_by_cert_lookup(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result **_result) +{ + return sysdb_search_user_by_cert_with_views(mem_ctx, domain, data->cert, + _result); +} + +static struct tevent_req * +cache_req_user_by_cert_dp_send(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result *result) +{ + return sss_dp_get_account_send(mem_ctx, cr->rctx, domain, true, + SSS_DP_CERT, cr->data->cert, 0, NULL); +} + +const struct cache_req_plugin cache_req_user_by_cert = { + .name = "User by certificate", + .attr_expiration = SYSDB_CACHE_EXPIRE, + .parse_name = false, + .ignore_default_domain = false, + .bypass_cache = false, + .only_one_result = false, + .search_all_domains = true, + .require_enumeration = false, + .allow_missing_fqn = true, + .allow_switch_to_upn = false, + .upn_equivalent = CACHE_REQ_SENTINEL, + .get_next_domain_flags = SSS_GND_DESCEND, + + .is_well_known_fn = NULL, + .prepare_domain_data_fn = NULL, + .create_debug_name_fn = cache_req_user_by_cert_create_debug_name, + .global_ncache_add_fn = cache_req_user_by_cert_global_ncache_add, + .ncache_check_fn = cache_req_user_by_cert_ncache_check, + .ncache_add_fn = NULL, + .ncache_filter_fn = NULL, + .lookup_fn = cache_req_user_by_cert_lookup, + .dp_send_fn = cache_req_user_by_cert_dp_send, + .dp_recv_fn = cache_req_common_dp_recv, + .dp_get_domain_check_fn = NULL, + .dp_get_domain_send_fn = NULL, + .dp_get_domain_recv_fn = NULL, +}; + +struct tevent_req * +cache_req_user_by_cert_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + enum cache_req_dom_type req_dom_type, + const char *domain, + const char *pem_cert) +{ + struct cache_req_data *data; + + data = cache_req_data_cert(mem_ctx, CACHE_REQ_USER_BY_CERT, pem_cert); + if (data == NULL) { + return NULL; + } + + return cache_req_steal_data_and_send(mem_ctx, ev, rctx, ncache, + cache_refresh_percent, + req_dom_type, domain, + data); +} diff --git a/src/responder/common/cache_req/plugins/cache_req_user_by_filter.c b/src/responder/common/cache_req/plugins/cache_req_user_by_filter.c new file mode 100644 index 0000000..11ea9ec --- /dev/null +++ b/src/responder/common/cache_req/plugins/cache_req_user_by_filter.c @@ -0,0 +1,176 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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 <talloc.h> +#include <ldb.h> + +#include "db/sysdb.h" +#include "util/util.h" +#include "providers/data_provider.h" +#include "responder/common/cache_req/cache_req_plugin.h" + +static errno_t +cache_req_user_by_filter_prepare_domain_data(struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + TALLOC_CTX *tmp_ctx; + const char *name; + errno_t ret; + + if (cr->data->name.name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: parsed name is NULL?\n"); + return ERR_INTERNAL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + name = sss_get_cased_name(tmp_ctx, cr->data->name.name, + domain->case_sensitive); + if (name == NULL) { + ret = ENOMEM; + goto done; + } + + name = sss_reverse_replace_space(tmp_ctx, name, cr->rctx->override_space); + if (name == NULL) { + ret = ENOMEM; + goto done; + } + + talloc_zfree(data->name.lookup); + data->name.lookup = talloc_steal(data, name); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static const char * +cache_req_user_by_filter_create_debug_name(TALLOC_CTX *mem_ctx, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + return talloc_strdup(mem_ctx, data->name.lookup); +} + +static errno_t +cache_req_user_by_filter_lookup(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result **_result) +{ + char *recent_filter; + const char *attr = (data->name.attr == NULL ? SYSDB_NAME : data->name.attr); + errno_t ret; + + /* The "files" provider updates the record if /etc/passwd or /etc/group + * is touched. It does not perform any per-request update. + * Therefore the last update flag is not updated if no file was touched + * and we cannot use this optimization. + * Neither it is possible to use it when asking for a non-"name" attribute + * as it could not be present in the timestamp cache. + */ + if (is_files_provider(domain) || data->name.attr != NULL) { + recent_filter = NULL; + } else { + recent_filter = talloc_asprintf(mem_ctx, "(%s>=%lu)", SYSDB_LAST_UPDATE, + cr->req_start); + if (recent_filter == NULL) { + return ENOMEM; + } + } + + ret = sysdb_enumpwent_filter_with_views(mem_ctx, domain, + attr, data->name.lookup, + recent_filter, _result); + talloc_free(recent_filter); + + return ret; +} + +static struct tevent_req * +cache_req_user_by_filter_dp_send(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result *result) +{ + return sss_dp_get_account_send(mem_ctx, cr->rctx, domain, true, + SSS_DP_WILDCARD_USER, cr->data->name.lookup, + cr->data->id, NULL); +} + +const struct cache_req_plugin cache_req_user_by_filter = { + .name = "User by filter", + .attr_expiration = SYSDB_CACHE_EXPIRE, + .parse_name = true, + .ignore_default_domain = false, + .bypass_cache = true, + .only_one_result = false, + .search_all_domains = false, + .require_enumeration = false, + .allow_missing_fqn = false, + .allow_switch_to_upn = false, + .upn_equivalent = CACHE_REQ_SENTINEL, + .get_next_domain_flags = SSS_GND_DESCEND, + + .is_well_known_fn = NULL, + .prepare_domain_data_fn = cache_req_user_by_filter_prepare_domain_data, + .create_debug_name_fn = cache_req_user_by_filter_create_debug_name, + .global_ncache_add_fn = NULL, + .ncache_check_fn = NULL, + .ncache_add_fn = NULL, + .ncache_filter_fn = NULL, + .lookup_fn = cache_req_user_by_filter_lookup, + .dp_send_fn = cache_req_user_by_filter_dp_send, + .dp_recv_fn = cache_req_common_dp_recv, + .dp_get_domain_check_fn = NULL, + .dp_get_domain_send_fn = NULL, + .dp_get_domain_recv_fn = NULL, +}; + +struct tevent_req * +cache_req_user_by_filter_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + enum cache_req_dom_type req_dom_type, + const char *domain, + const char *attr, + const char *filter) +{ + struct cache_req_data *data; + + data = cache_req_data_attr(mem_ctx, CACHE_REQ_USER_BY_FILTER, attr, filter); + if (data == NULL) { + return NULL; + } + + return cache_req_steal_data_and_send(mem_ctx, ev, rctx, NULL, + 0, + req_dom_type, domain, + data); +} diff --git a/src/responder/common/cache_req/plugins/cache_req_user_by_id.c b/src/responder/common/cache_req/plugins/cache_req_user_by_id.c new file mode 100644 index 0000000..fee5736 --- /dev/null +++ b/src/responder/common/cache_req/plugins/cache_req_user_by_id.c @@ -0,0 +1,246 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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 <talloc.h> +#include <ldb.h> + +#include "db/sysdb.h" +#include "util/util.h" +#include "providers/data_provider.h" +#include "responder/common/cache_req/cache_req_plugin.h" + +static const char * +cache_req_user_by_id_create_debug_name(TALLOC_CTX *mem_ctx, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + return talloc_asprintf(mem_ctx, "UID:%"PRIu32"@%s", data->id, domain->name); +} + +static errno_t +cache_req_user_by_id_ncache_check(struct sss_nc_ctx *ncache, + struct sss_domain_info *domain, + struct cache_req_data *data) +{ + errno_t ret; + + if (domain != NULL) { + ret = sss_ncache_check_uid(ncache, domain, data->id); + if (ret == EEXIST) { + return ret; + } + } + + return sss_ncache_check_uid(ncache, NULL, data->id); +} + +static errno_t +cache_req_user_by_id_ncache_filter(struct sss_nc_ctx *ncache, + struct sss_domain_info *domain, + const char *name) +{ + return sss_ncache_check_user(ncache, domain, name); +} + +static errno_t +cache_req_user_by_id_global_ncache_add(struct sss_nc_ctx *ncache, + struct cache_req_data *data) +{ + return sss_ncache_set_uid(ncache, false, NULL, data->id); +} + +static errno_t +cache_req_user_by_id_ncache_add(struct sss_nc_ctx *ncache, + struct sss_domain_info *domain, + struct cache_req_data *data) +{ + return sss_ncache_set_uid(ncache, false, domain, data->id); +} + +static errno_t +cache_req_user_by_id_lookup(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result **_result) +{ + errno_t ret; + ret = cache_req_idminmax_check(data, domain); + if (ret != EOK) { + return ret; + } + return sysdb_getpwuid_with_views(mem_ctx, domain, data->id, _result); +} + +static errno_t +cache_req_user_by_id_dpreq_params(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct ldb_result *result, + const char **_string, + uint32_t *_id, + const char **_flag) +{ + uint32_t id; + + *_id = cr->data->id; + *_string = NULL; + *_flag = NULL; + + if (!DOM_HAS_VIEWS(cr->domain)) { + return EOK; + } + + /* We must search with views. */ + if (result == NULL || result->count == 0) { + *_flag = EXTRA_INPUT_MAYBE_WITH_VIEW; + return EOK; + } + + /* If domain has views we will try to use original values instead of the + * overridden ones. This is a must for the LOCAL view since we can't look + * it up otherwise. But it is also a shortcut for non-local views where + * we will not fail over to the overridden value. */ + + id = ldb_msg_find_attr_as_uint64(result->msgs[0], SYSDB_UIDNUM, 0); + if (id == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: id cannot be 0\n"); + *_flag = EXTRA_INPUT_MAYBE_WITH_VIEW; + return EOK; + } + + /* Now we have the original name and id. We don't have to search with + * views unless some error occurred. */ + *_id = id; + + return EOK; +} + +static struct tevent_req * +cache_req_user_by_id_dp_send(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result *result) +{ + const char *string; + const char *flag; + uint32_t id; + errno_t ret; + + ret = cache_req_user_by_id_dpreq_params(mem_ctx, cr, result, + &string, &id, &flag); + if (ret != EOK) { + return NULL; + } + + return sss_dp_get_account_send(mem_ctx, cr->rctx, domain, true, + SSS_DP_USER, string, id, flag); +} + +static bool +cache_req_user_by_id_get_domain_check(struct resp_ctx *rctx, + struct sss_domain_info *domain, + struct cache_req_data *data) +{ + int nret; + + nret = sss_ncache_check_locate_uid(rctx->ncache, domain, data->id); + if (nret == EEXIST) { + return false; + } + + return true; +} + +static struct tevent_req * +cache_req_user_by_id_get_domain_send(TALLOC_CTX *mem_ctx, + struct resp_ctx *rctx, + struct sss_domain_info *domain, + struct cache_req_data *data) +{ + int nret; + + nret = sss_ncache_set_locate_uid(rctx->ncache, domain, data->id); + if (nret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot set negative cache, this might result in " + "performance degradation\n"); + /* Not fatal */ + } + + return sss_dp_get_account_domain_send(mem_ctx, + rctx, + domain, + true, /* fast_reply */ + SSS_DP_USER, + data->id, + NULL); +} + +const struct cache_req_plugin cache_req_user_by_id = { + .name = "User by ID", + .attr_expiration = SYSDB_CACHE_EXPIRE, + .parse_name = false, + .ignore_default_domain = false, + .bypass_cache = false, + .only_one_result = true, + .search_all_domains = false, + .require_enumeration = false, + .allow_missing_fqn = true, + .allow_switch_to_upn = false, + .upn_equivalent = CACHE_REQ_SENTINEL, + .get_next_domain_flags = SSS_GND_DESCEND, + + .is_well_known_fn = NULL, + .prepare_domain_data_fn = NULL, + .create_debug_name_fn = cache_req_user_by_id_create_debug_name, + .global_ncache_add_fn = cache_req_user_by_id_global_ncache_add, + .ncache_check_fn = cache_req_user_by_id_ncache_check, + .ncache_add_fn = cache_req_user_by_id_ncache_add, + .ncache_filter_fn = cache_req_user_by_id_ncache_filter, + .lookup_fn = cache_req_user_by_id_lookup, + .dp_send_fn = cache_req_user_by_id_dp_send, + .dp_recv_fn = cache_req_common_dp_recv, + .dp_get_domain_check_fn = cache_req_user_by_id_get_domain_check, + .dp_get_domain_send_fn = cache_req_user_by_id_get_domain_send, + .dp_get_domain_recv_fn = cache_req_common_get_acct_domain_recv, +}; + +struct tevent_req * +cache_req_user_by_id_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + const char *domain, + uid_t uid) +{ + struct cache_req_data *data; + + data = cache_req_data_id(mem_ctx, CACHE_REQ_USER_BY_ID, uid); + if (data == NULL) { + return NULL; + } + + return cache_req_steal_data_and_send(mem_ctx, ev, rctx, ncache, + cache_refresh_percent, + CACHE_REQ_POSIX_DOM, domain, + data); +} diff --git a/src/responder/common/cache_req/plugins/cache_req_user_by_name.c b/src/responder/common/cache_req/plugins/cache_req_user_by_name.c new file mode 100644 index 0000000..d24a222 --- /dev/null +++ b/src/responder/common/cache_req/plugins/cache_req_user_by_name.c @@ -0,0 +1,256 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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 <talloc.h> +#include <ldb.h> + +#include "db/sysdb.h" +#include "util/util.h" +#include "providers/data_provider.h" +#include "responder/common/cache_req/cache_req_plugin.h" + +static errno_t +cache_req_user_by_name_prepare_domain_data(struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + TALLOC_CTX *tmp_ctx; + const char *name; + errno_t ret; + + if (cr->data->name.name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: parsed name is NULL?\n"); + return ERR_INTERNAL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + name = sss_get_cased_name(tmp_ctx, cr->data->name.name, + domain->case_sensitive); + if (name == NULL) { + ret = ENOMEM; + goto done; + } + + name = sss_reverse_replace_space(tmp_ctx, name, cr->rctx->override_space); + if (name == NULL) { + ret = ENOMEM; + goto done; + } + + name = sss_create_internal_fqname(tmp_ctx, name, domain->name); + if (name == NULL) { + ret = ENOMEM; + goto done; + } + + talloc_zfree(data->name.lookup); + data->name.lookup = talloc_steal(data, name); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static const char * +cache_req_user_by_name_create_debug_name(TALLOC_CTX *mem_ctx, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + return talloc_strdup(mem_ctx, data->name.lookup); +} + +static errno_t +cache_req_user_by_name_ncache_check(struct sss_nc_ctx *ncache, + struct sss_domain_info *domain, + struct cache_req_data *data) +{ + return sss_ncache_check_user(ncache, domain, data->name.lookup); +} + +static errno_t +cache_req_user_by_name_ncache_add(struct sss_nc_ctx *ncache, + struct sss_domain_info *domain, + struct cache_req_data *data) +{ + return sss_ncache_set_user(ncache, false, domain, data->name.lookup); +} + +static errno_t +cache_req_user_by_name_lookup(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result **_result) +{ + if (data->attrs == NULL) { + return sysdb_getpwnam_with_views(mem_ctx, domain, data->name.lookup, + _result); + } + + return sysdb_get_user_attr_with_views(mem_ctx, domain, data->name.lookup, + data->attrs, _result); +} + +static errno_t +cache_req_user_by_name_dpreq_params(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct ldb_result *result, + const char **_string, + uint32_t *_id, + const char **_flag) +{ + const char *name; + + *_id = 0; + *_string = cr->data->name.lookup; + *_flag = NULL; + + if (!DOM_HAS_VIEWS(cr->domain)) { + return EOK; + } + + /* We must search with views. */ + if (result == NULL || result->count == 0) { + *_flag = EXTRA_INPUT_MAYBE_WITH_VIEW; + return EOK; + } + + /* If domain has views we will try to use original values instead of the + * overridden ones. This is a must for the LOCAL view since we can't look + * it up otherwise. But it is also a shortcut for non-local views where + * we will not fail over to the overridden value. */ + + name = ldb_msg_find_attr_as_string(result->msgs[0], SYSDB_NAME, NULL); + if (name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: name cannot be NULL\n"); + *_flag = EXTRA_INPUT_MAYBE_WITH_VIEW; + return EOK; + } + + /* Now we have the original name and id. We don't have to search with + * views unless some error occurred. */ + *_string = talloc_steal(mem_ctx, name); + + return EOK; +} + +static struct tevent_req * +cache_req_user_by_name_dp_send(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result *result) +{ + const char *string; + const char *flag; + uint32_t id; + errno_t ret; + + ret = cache_req_user_by_name_dpreq_params(mem_ctx, cr, result, + &string, &id, &flag); + if (ret != EOK) { + return NULL; + } + + return sss_dp_get_account_send(mem_ctx, cr->rctx, domain, true, + SSS_DP_USER, string, id, flag); +} + +const struct cache_req_plugin cache_req_user_by_name = { + .name = "User by name", + .attr_expiration = SYSDB_CACHE_EXPIRE, + .parse_name = true, + .ignore_default_domain = false, + .bypass_cache = false, + .only_one_result = true, + .search_all_domains = false, + .require_enumeration = false, + .allow_missing_fqn = false, + .allow_switch_to_upn = true, + .upn_equivalent = CACHE_REQ_USER_BY_UPN, + .get_next_domain_flags = SSS_GND_DESCEND, + + .is_well_known_fn = NULL, + .prepare_domain_data_fn = cache_req_user_by_name_prepare_domain_data, + .create_debug_name_fn = cache_req_user_by_name_create_debug_name, + .global_ncache_add_fn = NULL, + .ncache_check_fn = cache_req_user_by_name_ncache_check, + .ncache_add_fn = cache_req_user_by_name_ncache_add, + .ncache_filter_fn = NULL, + .lookup_fn = cache_req_user_by_name_lookup, + .dp_send_fn = cache_req_user_by_name_dp_send, + .dp_recv_fn = cache_req_common_dp_recv, + .dp_get_domain_check_fn = NULL, + .dp_get_domain_send_fn = NULL, + .dp_get_domain_recv_fn = NULL, +}; + +struct tevent_req * +cache_req_user_by_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + enum cache_req_dom_type req_dom_type, + const char *domain, + const char *name) +{ + struct cache_req_data *data; + + data = cache_req_data_name(mem_ctx, CACHE_REQ_USER_BY_NAME, name); + if (data == NULL) { + return NULL; + } + + return cache_req_steal_data_and_send(mem_ctx, ev, rctx, ncache, + cache_refresh_percent, + req_dom_type, domain, + data); +} + +struct tevent_req * +cache_req_user_by_name_attrs_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + const char *domain, + const char *name, + const char **attrs) +{ + struct cache_req_data *data; + + data = cache_req_data_name_attrs(mem_ctx, CACHE_REQ_USER_BY_NAME, + name, attrs); + if (data == NULL) { + return NULL; + } + + return cache_req_steal_data_and_send(mem_ctx, ev, rctx, ncache, + cache_refresh_percent, + CACHE_REQ_POSIX_DOM, domain, + data); +} diff --git a/src/responder/common/cache_req/plugins/cache_req_user_by_upn.c b/src/responder/common/cache_req/plugins/cache_req_user_by_upn.c new file mode 100644 index 0000000..037994c --- /dev/null +++ b/src/responder/common/cache_req/plugins/cache_req_user_by_upn.c @@ -0,0 +1,158 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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 <talloc.h> +#include <ldb.h> + +#include "db/sysdb.h" +#include "util/util.h" +#include "providers/data_provider.h" +#include "responder/common/cache_req/cache_req_plugin.h" + +static errno_t +cache_req_user_by_upn_prepare_domain_data(struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + const char *name; + + if (cr->data->name.name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: parsed UPN is NULL?\n"); + return ERR_INTERNAL; + } + + /* When looking up UPNs we don't want to reverse-replace spaces, + * just search whatever the user passed in. strdup the name so we + * can safely steal it later. + */ + name = talloc_strdup(data, cr->data->name.name); + if (name == NULL) { + return ENOMEM; + } + + talloc_zfree(data->name.lookup); + data->name.lookup = talloc_steal(data, name); + + return EOK; +} + +static const char * +cache_req_user_by_upn_create_debug_name(TALLOC_CTX *mem_ctx, + struct cache_req_data *data, + struct sss_domain_info *domain) +{ + return talloc_strdup(mem_ctx, data->name.lookup); +} + +static errno_t +cache_req_user_by_upn_ncache_check(struct sss_nc_ctx *ncache, + struct sss_domain_info *domain, + struct cache_req_data *data) +{ + return sss_ncache_check_upn(ncache, domain, data->name.lookup); +} + +static errno_t +cache_req_user_by_upn_ncache_add(struct sss_nc_ctx *ncache, + struct sss_domain_info *domain, + struct cache_req_data *data) +{ + return sss_ncache_set_upn(ncache, false, domain, data->name.lookup); +} + +static errno_t +cache_req_user_by_upn_lookup(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result **_result) +{ + if (data->attrs == NULL) { + return sysdb_getpwupn(mem_ctx, domain, true, data->name.lookup, _result); + } + + return sysdb_search_user_by_upn_res(mem_ctx, domain, true, + data->name.lookup, data->attrs, + _result); +} + +static struct tevent_req * +cache_req_user_by_upn_dp_send(TALLOC_CTX *mem_ctx, + struct cache_req *cr, + struct cache_req_data *data, + struct sss_domain_info *domain, + struct ldb_result *result) +{ + return sss_dp_get_account_send(mem_ctx, cr->rctx, domain, true, + SSS_DP_USER, cr->data->name.lookup, + 0, EXTRA_NAME_IS_UPN); +} + +const struct cache_req_plugin cache_req_user_by_upn = { + .name = "User by UPN", + .attr_expiration = SYSDB_CACHE_EXPIRE, + .parse_name = false, + .ignore_default_domain = false, + .bypass_cache = false, + .only_one_result = true, + .search_all_domains = false, + .require_enumeration = false, + .allow_missing_fqn = true, + .allow_switch_to_upn = false, + .upn_equivalent = CACHE_REQ_SENTINEL, + .get_next_domain_flags = SSS_GND_DESCEND, + + .is_well_known_fn = NULL, + .prepare_domain_data_fn = cache_req_user_by_upn_prepare_domain_data, + .create_debug_name_fn = cache_req_user_by_upn_create_debug_name, + .global_ncache_add_fn = NULL, + .ncache_check_fn = cache_req_user_by_upn_ncache_check, + .ncache_add_fn = cache_req_user_by_upn_ncache_add, + .ncache_filter_fn = NULL, + .lookup_fn = cache_req_user_by_upn_lookup, + .dp_send_fn = cache_req_user_by_upn_dp_send, + .dp_recv_fn = cache_req_common_dp_recv, + .dp_get_domain_check_fn = NULL, + .dp_get_domain_send_fn = NULL, + .dp_get_domain_recv_fn = NULL, +}; + +struct tevent_req * +cache_req_user_by_upn_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + int cache_refresh_percent, + enum cache_req_dom_type req_dom_type, + const char *domain, + const char *upn) +{ + struct cache_req_data *data; + + data = cache_req_data_name(mem_ctx, CACHE_REQ_USER_BY_UPN, upn); + if (data == NULL) { + return NULL; + } + + return cache_req_steal_data_and_send(mem_ctx, ev, rctx, ncache, + cache_refresh_percent, + req_dom_type, domain, + data); +} diff --git a/src/responder/common/negcache.c b/src/responder/common/negcache.c new file mode 100644 index 0000000..9ed0196 --- /dev/null +++ b/src/responder/common/negcache.c @@ -0,0 +1,1417 @@ +/* + SSSD + + NSS Responder + + Copyright (C) Simo Sorce <ssorce@redhat.com> 2008 + + 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 <fcntl.h> +#include <time.h> +#include "tdb.h" +#include "util/util.h" +#include "util/nss_dl_load.h" +#include "confdb/confdb.h" +#include "responder/common/negcache_files.h" +#include "responder/common/responder.h" +#include "responder/common/negcache.h" + + +#define NC_ENTRY_PREFIX "NCE/" +#define NC_USER_PREFIX NC_ENTRY_PREFIX"USER" +#define NC_GROUP_PREFIX NC_ENTRY_PREFIX"GROUP" +#define NC_NETGROUP_PREFIX NC_ENTRY_PREFIX"NETGR" +#define NC_SERVICE_PREFIX NC_ENTRY_PREFIX"SERVICE" +#define NC_UID_PREFIX NC_ENTRY_PREFIX"UID" +#define NC_GID_PREFIX NC_ENTRY_PREFIX"GID" +#define NC_SID_PREFIX NC_ENTRY_PREFIX"SID" +#define NC_CERT_PREFIX NC_ENTRY_PREFIX"CERT" +#define NC_DOMAIN_ACCT_LOCATE_PREFIX NC_ENTRY_PREFIX"DOM_LOCATE" +#define NC_DOMAIN_ACCT_LOCATE_TYPE_PREFIX NC_ENTRY_PREFIX"DOM_LOCATE_TYPE" + +struct sss_nc_ctx { + struct tdb_context *tdb; + uint32_t timeout; + uint32_t local_timeout; + struct sss_nss_ops ops; +}; + +typedef int (*ncache_set_byname_fn_t)(struct sss_nc_ctx *, bool, + const char *, const char *); + +static int sss_ncache_set_ent(struct sss_nc_ctx *ctx, bool permanent, + struct sss_domain_info *dom, const char *name, + ncache_set_byname_fn_t setter); + +static int string_to_tdb_data(char *str, TDB_DATA *ret) +{ + if (!str || !ret) return EINVAL; + + ret->dptr = (uint8_t *)str; + ret->dsize = strlen(str)+1; + + return EOK; +} + +static errno_t ncache_load_nss_symbols(struct sss_nss_ops *ops) +{ + errno_t ret; + struct sss_nss_symbols syms[] = { + {(void*)&ops->getpwnam_r, true, "getpwnam_r" }, + {(void*)&ops->getpwuid_r, true, "getpwuid_r" }, + {(void*)&ops->getgrnam_r, true, "getgrnam_r" }, + {(void*)&ops->getgrgid_r, true, "getgrgid_r" } + }; + size_t nsyms = sizeof(syms) / sizeof(struct sss_nss_symbols); + + ret = sss_load_nss_symbols(ops, "files", syms, nsyms); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +int sss_ncache_init(TALLOC_CTX *memctx, uint32_t timeout, + uint32_t local_timeout, struct sss_nc_ctx **_ctx) +{ + errno_t ret; + struct sss_nc_ctx *ctx; + + ctx = talloc_zero(memctx, struct sss_nc_ctx); + if (!ctx) return ENOMEM; + + ret = ncache_load_nss_symbols(&ctx->ops); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to load NSS symbols [%d]: %s\n", + ret, sss_strerror(ret)); + talloc_free(ctx); + return ret; + } + + errno = 0; + /* open a memory only tdb with default hash size */ + ctx->tdb = tdb_open("memcache", 0, TDB_INTERNAL, O_RDWR|O_CREAT, 0); + if (!ctx->tdb) return errno; + + ctx->timeout = timeout; + ctx->local_timeout = local_timeout; + + *_ctx = ctx; + return EOK; +}; + +uint32_t sss_ncache_get_timeout(struct sss_nc_ctx *ctx) +{ + return ctx->timeout; +} + +static int sss_ncache_check_str(struct sss_nc_ctx *ctx, char *str) +{ + TDB_DATA key; + TDB_DATA data; + unsigned long long int timestamp; + bool expired = false; + char *ep; + int ret; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Checking negative cache for [%s]\n", str); + + data.dptr = NULL; + + ret = string_to_tdb_data(str, &key); + if (ret != EOK) goto done; + + data = tdb_fetch(ctx->tdb, key); + + if (!data.dptr) { + ret = ENOENT; + goto done; + } + + errno = 0; + timestamp = strtoull((const char *)data.dptr, &ep, 10); + if (errno != 0 || *ep != '\0') { + /* Malformed entry, remove it and return no entry */ + expired = true; + goto done; + } + + if (timestamp == 0) { + /* a 0 timestamp means this is a permanent entry */ + ret = EEXIST; + goto done; + } + + if (timestamp >= time(NULL)) { + /* still valid */ + ret = EEXIST; + goto done; + } + + expired = true; + +done: + if (expired) { + /* expired, remove and return no entry */ + tdb_delete(ctx->tdb, key); + ret = ENOENT; + } + + free(data.dptr); + return ret; +} + +static int sss_ncache_set_str(struct sss_nc_ctx *ctx, char *str, + bool permanent, bool use_local_negative) +{ + TDB_DATA key; + TDB_DATA data; + char *timest; + unsigned long long int timell; + int ret; + + ret = string_to_tdb_data(str, &key); + if (ret != EOK) return ret; + + if (permanent) { + timest = talloc_strdup(ctx, "0"); + } else { + if (use_local_negative == true && ctx->local_timeout > ctx->timeout) { + timell = ctx->local_timeout; + } else { + /* EOK is tested in cwrap based unit test */ + if (ctx->timeout == 0) { + return EOK; + } + timell = ctx->timeout; + } + timell += (unsigned long long int)time(NULL); + timest = talloc_asprintf(ctx, "%llu", timell); + } + if (!timest) return ENOMEM; + + ret = string_to_tdb_data(timest, &data); + if (ret != EOK) goto done; + + DEBUG(SSSDBG_TRACE_FUNC, "Adding [%s] to negative cache%s\n", + str, permanent?" permanently":""); + + ret = tdb_store(ctx->tdb, key, data, TDB_REPLACE); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Negative cache failed to set entry: [%s]\n", + tdb_errorstr(ctx->tdb)); + ret = EFAULT; + } + +done: + talloc_free(timest); + return ret; +} + +static int sss_ncache_check_user_int(struct sss_nc_ctx *ctx, const char *domain, + const char *name) +{ + char *str; + int ret; + + if (!name || !*name) return EINVAL; + + str = talloc_asprintf(ctx, "%s/%s/%s", NC_USER_PREFIX, domain, name); + if (!str) return ENOMEM; + + ret = sss_ncache_check_str(ctx, str); + + talloc_free(str); + return ret; +} + +static int sss_ncache_check_group_int(struct sss_nc_ctx *ctx, + const char *domain, const char *name) +{ + char *str; + int ret; + + if (!name || !*name) return EINVAL; + + str = talloc_asprintf(ctx, "%s/%s/%s", NC_GROUP_PREFIX, domain, name); + if (!str) return ENOMEM; + + ret = sss_ncache_check_str(ctx, str); + + talloc_free(str); + return ret; +} + +static int sss_ncache_check_netgr_int(struct sss_nc_ctx *ctx, + const char *domain, const char *name) +{ + char *str; + int ret; + + if (!name || !*name) return EINVAL; + + str = talloc_asprintf(ctx, "%s/%s/%s", NC_NETGROUP_PREFIX, domain, name); + if (!str) return ENOMEM; + + ret = sss_ncache_check_str(ctx, str); + + talloc_free(str); + return ret; +} + +static int sss_ncache_check_service_int(struct sss_nc_ctx *ctx, + const char *domain, + const char *name) +{ + char *str; + int ret; + + if (!name || !*name) return EINVAL; + + str = talloc_asprintf(ctx, "%s/%s/%s", + NC_SERVICE_PREFIX, + domain, + name); + if (!str) return ENOMEM; + + ret = sss_ncache_check_str(ctx, str); + + talloc_free(str); + return ret; +} + +typedef int (*ncache_check_byname_fn_t)(struct sss_nc_ctx *, const char *, + const char *); + +static int sss_cache_check_ent(struct sss_nc_ctx *ctx, + struct sss_domain_info *dom, const char *name, + ncache_check_byname_fn_t checker) +{ + char *lower; + errno_t ret; + + if (dom->case_sensitive == false) { + lower = sss_tc_utf8_str_tolower(ctx, name); + if (!lower) return ENOMEM; + ret = checker(ctx, dom->name, lower); + talloc_free(lower); + } else { + ret = checker(ctx, dom->name, name); + } + + return ret; +} + +int sss_ncache_check_user(struct sss_nc_ctx *ctx, struct sss_domain_info *dom, + const char *name) +{ + return sss_cache_check_ent(ctx, dom, name, sss_ncache_check_user_int); +} + +int sss_ncache_check_upn(struct sss_nc_ctx *ctx, struct sss_domain_info *dom, + const char *name) +{ + char *neg_cache_name = NULL; + errno_t ret; + + neg_cache_name = talloc_asprintf(ctx, "@%s", name); + if (neg_cache_name == NULL) { + return ENOMEM; + } + + ret = sss_cache_check_ent(ctx, dom, neg_cache_name, + sss_ncache_check_user_int); + talloc_free(neg_cache_name); + + return ret; +} + +int sss_ncache_check_group(struct sss_nc_ctx *ctx, struct sss_domain_info *dom, + const char *name) +{ + return sss_cache_check_ent(ctx, dom, name, sss_ncache_check_group_int); +} + +int sss_ncache_check_netgr(struct sss_nc_ctx *ctx, struct sss_domain_info *dom, + const char *name) +{ + return sss_cache_check_ent(ctx, dom, name, sss_ncache_check_netgr_int); +} + +static int sss_ncache_set_service_int(struct sss_nc_ctx *ctx, bool permanent, + const char *domain, const char *name) +{ + char *str; + int ret; + + if (!name || !*name) return EINVAL; + + str = talloc_asprintf(ctx, "%s/%s/%s", NC_SERVICE_PREFIX, domain, name); + if (!str) return ENOMEM; + + ret = sss_ncache_set_str(ctx, str, permanent, false); + + talloc_free(str); + return ret; +} + +int sss_ncache_set_service_name(struct sss_nc_ctx *ctx, bool permanent, + struct sss_domain_info *dom, + const char *name, const char *proto) +{ + int ret; + char *service_and_protocol = talloc_asprintf(ctx, "%s:%s", + name, + proto ? proto : "<ANY>"); + if (!service_and_protocol) return ENOMEM; + + ret = sss_ncache_set_ent(ctx, permanent, dom, + service_and_protocol, + sss_ncache_set_service_int); + talloc_free(service_and_protocol); + return ret; +} + +int sss_ncache_check_service(struct sss_nc_ctx *ctx,struct sss_domain_info *dom, + const char *name, const char *proto) +{ + int ret; + char *service_and_protocol = talloc_asprintf(ctx, "%s:%s", + name, + proto ? proto : "<ANY>"); + if (!service_and_protocol) return ENOMEM; + + ret = sss_cache_check_ent(ctx, dom, service_and_protocol, + sss_ncache_check_service_int); + talloc_free(service_and_protocol); + return ret; +} + +int sss_ncache_set_service_port(struct sss_nc_ctx *ctx, bool permanent, + struct sss_domain_info *dom, + uint16_t port, const char *proto) +{ + int ret; + char *service_and_protocol = talloc_asprintf(ctx, "%ul:%s", + port, + proto ? proto : "<ANY>"); + if (!service_and_protocol) return ENOMEM; + + ret = sss_ncache_set_ent(ctx, permanent, dom, + service_and_protocol, + sss_ncache_set_service_int); + talloc_free(service_and_protocol); + return ret; +} + +int sss_ncache_check_service_port(struct sss_nc_ctx *ctx, + struct sss_domain_info *dom, + uint16_t port, + const char *proto) +{ + int ret; + char *service_and_protocol = talloc_asprintf(ctx, "%ul:%s", + port, + proto ? proto : "<ANY>"); + if (!service_and_protocol) return ENOMEM; + + ret = sss_cache_check_ent(ctx, dom, service_and_protocol, + sss_ncache_check_service_int); + talloc_free(service_and_protocol); + return ret; +} + + + +int sss_ncache_check_uid(struct sss_nc_ctx *ctx, struct sss_domain_info *dom, + uid_t uid) +{ + char *str; + int ret; + + if (dom != NULL) { + str = talloc_asprintf(ctx, "%s/%s/%"SPRIuid, NC_UID_PREFIX, dom->name, + uid); + } else { + str = talloc_asprintf(ctx, "%s/%"SPRIuid, NC_UID_PREFIX, uid); + } + if (!str) return ENOMEM; + + ret = sss_ncache_check_str(ctx, str); + + talloc_free(str); + return ret; +} + +int sss_ncache_check_gid(struct sss_nc_ctx *ctx, struct sss_domain_info *dom, + gid_t gid) +{ + char *str; + int ret; + + if (dom != NULL) { + str = talloc_asprintf(ctx, "%s/%s/%"SPRIgid, NC_GID_PREFIX, dom->name, + gid); + } else { + str = talloc_asprintf(ctx, "%s/%"SPRIgid, NC_GID_PREFIX, gid); + } + if (!str) return ENOMEM; + + ret = sss_ncache_check_str(ctx, str); + + talloc_free(str); + return ret; +} + +int sss_ncache_check_sid(struct sss_nc_ctx *ctx, struct sss_domain_info *dom, + const char *sid) +{ + char *str; + int ret; + + if (dom != NULL) { + str = talloc_asprintf(ctx, "%s/%s/%s", NC_SID_PREFIX, dom->name, sid); + } else { + str = talloc_asprintf(ctx, "%s/%s", NC_SID_PREFIX, sid); + } + if (!str) return ENOMEM; + + ret = sss_ncache_check_str(ctx, str); + + talloc_free(str); + return ret; +} + +int sss_ncache_check_cert(struct sss_nc_ctx *ctx, const char *cert) +{ + char *str; + int ret; + + str = talloc_asprintf(ctx, "%s/%s", NC_CERT_PREFIX, cert); + if (!str) return ENOMEM; + + ret = sss_ncache_check_str(ctx, str); + + talloc_free(str); + return ret; +} + + +static int sss_ncache_set_user_int(struct sss_nc_ctx *ctx, bool permanent, + const char *domain, const char *name) +{ + bool use_local_negative = false; + char *str; + int ret; + + if (!name || !*name) return EINVAL; + + str = talloc_asprintf(ctx, "%s/%s/%s", NC_USER_PREFIX, domain, name); + if (!str) return ENOMEM; + + if ((!permanent) && (ctx->local_timeout > 0)) { + use_local_negative = is_user_local_by_name(&ctx->ops, name); + } + ret = sss_ncache_set_str(ctx, str, permanent, use_local_negative); + + talloc_free(str); + return ret; +} + +static int sss_ncache_set_group_int(struct sss_nc_ctx *ctx, bool permanent, + const char *domain, const char *name) +{ + bool use_local_negative = false; + char *str; + int ret; + + if (!name || !*name) return EINVAL; + + str = talloc_asprintf(ctx, "%s/%s/%s", NC_GROUP_PREFIX, domain, name); + if (!str) return ENOMEM; + + if ((!permanent) && (ctx->local_timeout > 0)) { + use_local_negative = is_group_local_by_name(&ctx->ops, name); + } + ret = sss_ncache_set_str(ctx, str, permanent, use_local_negative); + + talloc_free(str); + return ret; +} + +static int sss_ncache_set_netgr_int(struct sss_nc_ctx *ctx, bool permanent, + const char *domain, const char *name) +{ + char *str; + int ret; + + if (!name || !*name) return EINVAL; + + str = talloc_asprintf(ctx, "%s/%s/%s", NC_NETGROUP_PREFIX, domain, name); + if (!str) return ENOMEM; + + ret = sss_ncache_set_str(ctx, str, permanent, false); + + talloc_free(str); + return ret; +} + +static int sss_ncache_set_ent(struct sss_nc_ctx *ctx, bool permanent, + struct sss_domain_info *dom, const char *name, + ncache_set_byname_fn_t setter) +{ + char *lower; + errno_t ret; + + if (dom->case_sensitive == false) { + lower = sss_tc_utf8_str_tolower(ctx, name); + if (!lower) return ENOMEM; + ret = setter(ctx, permanent, dom->name, lower); + talloc_free(lower); + } else { + ret = setter(ctx, permanent, dom->name, name); + } + + return ret; +} + + +int sss_ncache_set_user(struct sss_nc_ctx *ctx, bool permanent, + struct sss_domain_info *dom, const char *name) +{ + return sss_ncache_set_ent(ctx, permanent, dom, name, sss_ncache_set_user_int); +} + +int sss_ncache_set_upn(struct sss_nc_ctx *ctx, bool permanent, + struct sss_domain_info *dom, const char *name) +{ + char *neg_cache_name = NULL; + errno_t ret; + + neg_cache_name = talloc_asprintf(ctx, "@%s", name); + if (neg_cache_name == NULL) { + return ENOMEM; + } + + ret = sss_ncache_set_ent(ctx, permanent, dom, neg_cache_name, + sss_ncache_set_user_int); + talloc_free(neg_cache_name); + + return ret; +} + +int sss_ncache_set_group(struct sss_nc_ctx *ctx, bool permanent, + struct sss_domain_info *dom, const char *name) +{ + return sss_ncache_set_ent(ctx, permanent, dom, name, sss_ncache_set_group_int); +} + +int sss_ncache_set_netgr(struct sss_nc_ctx *ctx, bool permanent, + struct sss_domain_info *dom, const char *name) +{ + return sss_ncache_set_ent(ctx, permanent, dom, name, sss_ncache_set_netgr_int); +} + +int sss_ncache_set_uid(struct sss_nc_ctx *ctx, bool permanent, + struct sss_domain_info *dom, uid_t uid) +{ + bool use_local_negative = false; + char *str; + int ret; + + if (dom != NULL) { + str = talloc_asprintf(ctx, "%s/%s/%"SPRIuid, NC_UID_PREFIX, dom->name, + uid); + } else { + str = talloc_asprintf(ctx, "%s/%"SPRIuid, NC_UID_PREFIX, uid); + } + if (!str) return ENOMEM; + + if ((!permanent) && (ctx->local_timeout > 0)) { + use_local_negative = is_user_local_by_uid(&ctx->ops, uid); + } + ret = sss_ncache_set_str(ctx, str, permanent, use_local_negative); + + talloc_free(str); + return ret; +} + +int sss_ncache_set_gid(struct sss_nc_ctx *ctx, bool permanent, + struct sss_domain_info *dom, gid_t gid) +{ + bool use_local_negative = false; + char *str; + int ret; + + if (dom != NULL) { + str = talloc_asprintf(ctx, "%s/%s/%"SPRIgid, NC_GID_PREFIX, dom->name, + gid); + } else { + str = talloc_asprintf(ctx, "%s/%"SPRIgid, NC_GID_PREFIX, gid); + } + if (!str) return ENOMEM; + + if ((!permanent) && (ctx->local_timeout > 0)) { + use_local_negative = is_group_local_by_gid(&ctx->ops, gid); + } + ret = sss_ncache_set_str(ctx, str, permanent, use_local_negative); + + talloc_free(str); + return ret; +} + +int sss_ncache_set_sid(struct sss_nc_ctx *ctx, bool permanent, + struct sss_domain_info *dom, const char *sid) +{ + char *str; + int ret; + + if (dom != NULL) { + str = talloc_asprintf(ctx, "%s/%s/%s", NC_SID_PREFIX, dom->name, sid); + } else { + str = talloc_asprintf(ctx, "%s/%s", NC_SID_PREFIX, sid); + } + if (!str) return ENOMEM; + + ret = sss_ncache_set_str(ctx, str, permanent, false); + + talloc_free(str); + return ret; +} + +int sss_ncache_set_cert(struct sss_nc_ctx *ctx, bool permanent, + const char *cert) +{ + char *str; + int ret; + + str = talloc_asprintf(ctx, "%s/%s", NC_CERT_PREFIX, cert); + if (!str) return ENOMEM; + + ret = sss_ncache_set_str(ctx, str, permanent, false); + + talloc_free(str); + return ret; +} + +static char *domain_lookup_type_str(TALLOC_CTX *mem_ctx, + struct sss_domain_info *dom, + const char *lookup_type) +{ + return talloc_asprintf(mem_ctx, + "%s/%s/%s", + NC_DOMAIN_ACCT_LOCATE_TYPE_PREFIX, + dom->name, + lookup_type); +} + +int sss_ncache_set_domain_locate_type(struct sss_nc_ctx *ctx, + struct sss_domain_info *dom, + const char *lookup_type) +{ + char *str; + int ret; + + str = domain_lookup_type_str(ctx, dom, lookup_type); + if (!str) return ENOMEM; + + /* Permanent cache is always used here, because the lookup + * type's (getgrgid, getpwuid, ..) support locating an entry's domain + * doesn't change + */ + ret = sss_ncache_set_str(ctx, str, true, false); + talloc_free(str); + return ret; +} + +int sss_ncache_check_domain_locate_type(struct sss_nc_ctx *ctx, + struct sss_domain_info *dom, + const char *lookup_type) +{ + char *str; + int ret; + + str = domain_lookup_type_str(ctx, dom, lookup_type); + if (!str) return ENOMEM; + + ret = sss_ncache_check_str(ctx, str); + talloc_free(str); + return ret; +} + +static char *locate_gid_str(TALLOC_CTX *mem_ctx, + struct sss_domain_info *dom, + gid_t gid) +{ + return talloc_asprintf(mem_ctx, + "%s/%s/%s/%"SPRIgid, + NC_DOMAIN_ACCT_LOCATE_PREFIX, + NC_GID_PREFIX, + dom->name, + gid); +} + +int sss_ncache_set_locate_gid(struct sss_nc_ctx *ctx, + struct sss_domain_info *dom, + gid_t gid) +{ + char *str; + int ret; + + if (dom == NULL) { + return EINVAL; + } + + str = locate_gid_str(ctx, dom, gid); + if (str == NULL) { + return ENOMEM; + } + + ret = sss_ncache_set_str(ctx, str, false, false); + talloc_free(str); + return ret; +} + +int sss_ncache_check_locate_gid(struct sss_nc_ctx *ctx, + struct sss_domain_info *dom, + gid_t gid) +{ + char *str; + int ret; + + if (dom == NULL) { + return EINVAL; + } + + str = locate_gid_str(ctx, dom, gid); + if (str == NULL) { + return ENOMEM; + } + + ret = sss_ncache_check_str(ctx, str); + talloc_free(str); + return ret; +} + +static char *locate_uid_str(struct sss_nc_ctx *ctx, + struct sss_domain_info *dom, + uid_t uid) +{ + return talloc_asprintf(ctx, + "%s/%s/%s/%"SPRIuid, + NC_DOMAIN_ACCT_LOCATE_PREFIX, + NC_UID_PREFIX, + dom->name, + uid); +} + +int sss_ncache_set_locate_uid(struct sss_nc_ctx *ctx, + struct sss_domain_info *dom, + uid_t uid) +{ + char *str; + int ret; + + if (dom == NULL) { + return EINVAL; + } + + str = locate_uid_str(ctx, dom, uid); + if (str == NULL) { + return ENOMEM; + } + + ret = sss_ncache_set_str(ctx, str, false, false); + talloc_free(str); + return ret; +} + +int sss_ncache_check_locate_uid(struct sss_nc_ctx *ctx, + struct sss_domain_info *dom, + uid_t uid) +{ + char *str; + int ret; + + if (dom == NULL) { + return EINVAL; + } + + str = locate_uid_str(ctx, dom, uid); + if (str == NULL) { + return ENOMEM; + } + + ret = sss_ncache_check_str(ctx, str); + talloc_free(str); + return ret; +} + +static char *locate_sid_str(struct sss_nc_ctx *ctx, + struct sss_domain_info *dom, + const char *sid) +{ + return talloc_asprintf(ctx, + "%s/%s/%s/%s", + NC_DOMAIN_ACCT_LOCATE_PREFIX, + NC_SID_PREFIX, + dom->name, + sid); +} + +int sss_ncache_check_locate_sid(struct sss_nc_ctx *ctx, + struct sss_domain_info *dom, + const char *sid) +{ + char *str; + int ret; + + if (dom == NULL) { + return EINVAL; + } + + str = locate_sid_str(ctx, dom, sid); + if (str == NULL) { + return ENOMEM; + } + + ret = sss_ncache_check_str(ctx, str); + talloc_free(str); + return ret; +} + +int sss_ncache_set_locate_sid(struct sss_nc_ctx *ctx, + struct sss_domain_info *dom, + const char *sid) +{ + char *str; + int ret; + + if (dom == NULL) { + return EINVAL; + } + + str = locate_sid_str(ctx, dom, sid); + if (str == NULL) { + return ENOMEM; + } + + ret = sss_ncache_set_str(ctx, str, false, false); + talloc_free(str); + return ret; +} + +static int delete_permanent(struct tdb_context *tdb, + TDB_DATA key, TDB_DATA data, void *state) +{ + unsigned long long int timestamp; + bool remove_key = false; + char *ep; + + if (strncmp((char *)key.dptr, + NC_ENTRY_PREFIX, sizeof(NC_ENTRY_PREFIX) - 1) != 0) { + /* not interested in this key */ + return 0; + } + + errno = 0; + timestamp = strtoull((const char *)data.dptr, &ep, 10); + if (errno != 0 || *ep != '\0') { + /* Malformed entry, remove it */ + remove_key = true; + goto done; + } + + if (timestamp == 0) { + /* a 0 timestamp means this is a permanent entry */ + remove_key = true; + } + +done: + if (remove_key) { + return tdb_delete(tdb, key); + } + + return 0; +} + +int sss_ncache_reset_permanent(struct sss_nc_ctx *ctx) +{ + int ret; + + ret = tdb_traverse(ctx->tdb, delete_permanent, NULL); + if (ret < 0) + return EIO; + + return EOK; +} + +static int delete_prefix(struct tdb_context *tdb, + TDB_DATA key, TDB_DATA data, void *state) +{ + const char *prefix = (const char *) state; + unsigned long long int timestamp; + char *ep = NULL; + + if (strncmp((char *)key.dptr, prefix, strlen(prefix) - 1) != 0) { + /* not interested in this key */ + return 0; + } + + errno = 0; + timestamp = strtoull((const char *)data.dptr, &ep, 10); + if ((errno == 0) && (*ep == '\0') && (timestamp == 0)) { + /* skip permanent entries */ + return 0; + } + + return tdb_delete(tdb, key); +} + +static int sss_ncache_reset_pfx(struct sss_nc_ctx *ctx, + const char **prefixes) +{ + int ret; + + if (prefixes == NULL) { + return EOK; + } + + for (int i = 0; prefixes[i] != NULL; i++) { + ret = tdb_traverse(ctx->tdb, + delete_prefix, + discard_const(prefixes[i])); + if (ret < 0) { + return EIO; + } + } + + return EOK; +} + +int sss_ncache_reset_users(struct sss_nc_ctx *ctx) +{ + const char *prefixes[] = { + NC_USER_PREFIX, + NC_UID_PREFIX, + NULL, + }; + + return sss_ncache_reset_pfx(ctx, prefixes); +} + +int sss_ncache_reset_groups(struct sss_nc_ctx *ctx) +{ + const char *prefixes[] = { + NC_GROUP_PREFIX, + NC_GID_PREFIX, + NULL, + }; + + return sss_ncache_reset_pfx(ctx, prefixes); +} + +errno_t sss_ncache_prepopulate(struct sss_nc_ctx *ncache, + struct confdb_ctx *cdb, + struct resp_ctx *rctx) +{ + errno_t ret; + char **filter_list = NULL; + char **default_list = NULL; + char *name = NULL; + struct sss_domain_info *dom = NULL; + struct sss_domain_info *domain_list = rctx->domains; + struct sss_domain_info *ddom; + char *domainname = NULL; + char *conf_path = NULL; + TALLOC_CTX *tmpctx = talloc_new(NULL); + int i; + char *fqname = NULL; + + if (tmpctx == NULL) { + return ENOMEM; + } + + /* Populate domain-specific negative cache user entries */ + for (dom = domain_list; dom; dom = get_next_domain(dom, 0)) { + conf_path = talloc_asprintf(tmpctx, CONFDB_DOMAIN_PATH_TMPL, + dom->name); + if (!conf_path) { + ret = ENOMEM; + goto done; + } + + talloc_zfree(filter_list); + ret = confdb_get_string_as_list(cdb, tmpctx, conf_path, + CONFDB_NSS_FILTER_USERS, + &filter_list); + if (ret == ENOENT) continue; + if (ret != EOK) goto done; + + for (i = 0; (filter_list && filter_list[i]); i++) { + ret = sss_parse_name_for_domains(tmpctx, domain_list, + NULL, + filter_list[i], + &domainname, &name); + if (ret == EAGAIN) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Can add [%s] only as UPN to negcache because the " + "required domain is not known yet\n", filter_list[i]); + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Invalid name in filterUsers list: [%s] (%d)\n", + filter_list[i], ret); + continue; + } + + /* Check domain and its sub-domains */ + for (ddom = dom; ddom != NULL; + ddom = get_next_domain(ddom, SSS_GND_ALL_SUBDOMAINS)) { + + if (domainname && strcmp(domainname, ddom->name)) { + DEBUG(SSSDBG_TRACE_FUNC, + "Mismatch between domain name (%s) and name " + "set in FQN (%s), assuming %s is UPN\n", + ddom->name, domainname, filter_list[i]); + ret = sss_ncache_set_upn(ncache, true, ddom, filter_list[i]); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_ncache_set_upn failed (%d [%s]), ignored\n", + ret, sss_strerror(ret)); + } + continue; + } + + fqname = sss_create_internal_fqname(tmpctx, name, ddom->name); + if (fqname == NULL) { + continue; + } + + ret = sss_ncache_set_upn(ncache, true, ddom, fqname); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_ncache_set_upn failed (%d [%s]), ignored\n", + ret, sss_strerror(ret)); + } + ret = sss_ncache_set_user(ncache, true, ddom, fqname); + talloc_zfree(fqname); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to store permanent user filter for [%s]" + " (%d [%s])\n", filter_list[i], + ret, sss_strerror(ret)); + continue; + } + } + } + } + + talloc_zfree(filter_list); + /* Populate non domain-specific negative cache user entries */ + ret = confdb_get_string_as_list(cdb, tmpctx, CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_FILTER_USERS, &filter_list); + if (ret != EOK && ret != ENOENT) { + goto done; + } + + for (i = 0; (filter_list && filter_list[i]); i++) { + ret = sss_parse_name_for_domains(tmpctx, domain_list, + NULL, filter_list[i], + &domainname, &name); + if (ret == EAGAIN) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Can add [%s] only as UPN to negcache because the " + "required domain is not known yet\n", filter_list[i]); + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Invalid name in filterUsers list: [%s] (%d)\n", + filter_list[i], ret); + continue; + } + if (domainname) { + DEBUG(SSSDBG_TRACE_ALL, + "Adding [%s] to UPN negative cache of all domains.\n", + filter_list[i]); + for (dom = domain_list; + dom != NULL; + dom = get_next_domain(dom, SSS_GND_ALL_DOMAINS)) { + ret = sss_ncache_set_upn(ncache, true, dom, filter_list[i]); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_ncache_set_upn failed (%d [%s]), ignored\n", + ret, sss_strerror(ret)); + } + } + + /* Add name to domain specific cache for known domain names */ + dom = responder_get_domain(rctx, domainname); + if (dom != NULL) { + fqname = sss_create_internal_fqname(tmpctx, name, dom->name); + if (fqname == NULL) { + continue; + } + + ret = sss_ncache_set_user(ncache, true, dom, fqname); + talloc_zfree(fqname); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to store permanent user filter for [%s]" + " (%d [%s])\n", filter_list[i], + ret, strerror(ret)); + continue; + } + } + } else { + for (dom = domain_list; + dom != NULL; + dom = get_next_domain(dom, SSS_GND_ALL_DOMAINS)) { + fqname = sss_create_internal_fqname(tmpctx, name, dom->name); + if (fqname == NULL) { + continue; + } + + ret = sss_ncache_set_upn(ncache, true, dom, fqname); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to store permanent upn filter for" + " [%s:%s] (%d [%s])\n", + dom->name, filter_list[i], + ret, strerror(ret)); + } + + ret = sss_ncache_set_user(ncache, true, dom, fqname); + talloc_zfree(fqname); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to store permanent user filter for" + " [%s:%s] (%d [%s])\n", + dom->name, filter_list[i], + ret, strerror(ret)); + continue; + } + } + } + } + + /* Populate domain-specific negative cache group entries */ + for (dom = domain_list; dom; dom = get_next_domain(dom, 0)) { + conf_path = talloc_asprintf(tmpctx, CONFDB_DOMAIN_PATH_TMPL, dom->name); + if (!conf_path) { + ret = ENOMEM; + goto done; + } + + talloc_zfree(filter_list); + ret = confdb_get_string_as_list(cdb, tmpctx, conf_path, + CONFDB_NSS_FILTER_GROUPS, &filter_list); + if (ret == ENOENT) continue; + if (ret != EOK) goto done; + + for (i = 0; (filter_list && filter_list[i]); i++) { + ret = sss_parse_name_for_domains(tmpctx, domain_list, + NULL, filter_list[i], + &domainname, &name); + if (ret != EOK) { + /* Groups do not have UPNs, so domain names, if present, + * must be known */ + DEBUG(SSSDBG_CRIT_FAILURE, + "Invalid name in filterGroups list: [%s] (%d)\n", + filter_list[i], ret); + continue; + } + + /* Check domain and its sub-domains */ + for (ddom = dom; + ddom != NULL && (ddom == dom || ddom->parent != NULL); + ddom = get_next_domain(ddom, SSS_GND_ALL_DOMAINS)) { + if (domainname && strcmp(domainname, ddom->name)) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Mismatch between domain name (%s) and name " + "set in FQN (%s), skipping group %s\n", + ddom->name, domainname, name); + continue; + } + + fqname = sss_create_internal_fqname(tmpctx, name, ddom->name); + if (fqname == NULL) { + continue; + } + + ret = sss_ncache_set_group(ncache, true, ddom, fqname); + talloc_zfree(fqname); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to store permanent group filter for [%s]" + " (%d [%s])\n", filter_list[i], + ret, strerror(ret)); + continue; + } + } + } + } + + talloc_zfree(filter_list); + /* Populate non domain-specific negative cache group entries */ + ret = confdb_get_string_as_list(cdb, tmpctx, CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_FILTER_GROUPS, &filter_list); + if (ret != EOK && ret != ENOENT) { + goto done; + } + + for (i = 0; (filter_list && filter_list[i]); i++) { + ret = sss_parse_name_for_domains(tmpctx, domain_list, + NULL, filter_list[i], + &domainname, &name); + if (ret != EOK) { + /* Groups do not have UPNs, so domain names, if present, + * must be known */ + DEBUG(SSSDBG_CRIT_FAILURE, + "Invalid name in filterGroups list: [%s] (%d)\n", + filter_list[i], ret); + continue; + } + if (domainname) { + dom = responder_get_domain(rctx, domainname); + if (!dom) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Invalid domain name [%s]\n", domainname); + continue; + } + + fqname = sss_create_internal_fqname(tmpctx, name, dom->name); + if (fqname == NULL) { + continue; + } + + ret = sss_ncache_set_group(ncache, true, dom, fqname); + talloc_zfree(fqname); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to store permanent group filter for" + " [%s] (%d [%s])\n", filter_list[i], + ret, strerror(ret)); + continue; + } + } else { + for (dom = domain_list; + dom != NULL; + dom = get_next_domain(dom, SSS_GND_ALL_DOMAINS)) { + fqname = sss_create_internal_fqname(tmpctx, name, dom->name); + if (fqname == NULL) { + continue; + } + + ret = sss_ncache_set_group(ncache, true, dom, fqname); + talloc_zfree(fqname); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to store permanent group filter for" + " [%s:%s] (%d [%s])\n", + dom->name, filter_list[i], + ret, strerror(ret)); + continue; + } + } + } + } + + /* SSSD doesn't handle "root", thus it'll be added to the negative cache + * nonetheless what's already added there. */ + default_list = talloc_array(tmpctx, char *, 2); + if (default_list == NULL) { + ret= ENOMEM; + goto done; + } + default_list[0] = talloc_strdup(tmpctx, "root"); + if (default_list[0] == NULL) { + ret = ENOMEM; + goto done; + } + default_list[1] = NULL; + + /* Populate negative cache users and groups entries for the + * "default_list" */ + for (i = 0; (default_list != NULL && default_list[i] != NULL); i++) { + for (dom = domain_list; + dom != NULL; + dom = get_next_domain(dom, SSS_GND_ALL_DOMAINS)) { + fqname = sss_create_internal_fqname(tmpctx, + default_list[i], + dom->name); + if (fqname == NULL) { + continue; + } + + ret = sss_ncache_set_user(ncache, true, dom, fqname); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to store permanent user filter for" + " [%s:%s] (%d [%s])\n", + dom->name, default_list[i], + ret, strerror(ret)); + continue; + } + + ret = sss_ncache_set_group(ncache, true, dom, fqname); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to store permanent group filter for" + " [%s:%s] (%d [%s])\n", + dom->name, default_list[i], + ret, strerror(ret)); + continue; + } + } + } + + /* Also add "root" uid and gid to the negative cache */ + ret = sss_ncache_set_uid(ncache, true, NULL, 0); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to store permanent uid filter for root (0) " + "(%d [%s])\n", + ret, strerror(ret)); + } + + ret = sss_ncache_set_gid(ncache, true, NULL, 0); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to store permanent gid filter for root (0) " + "(%d [%s])\n", + ret, strerror(ret)); + } + + ret = EOK; + +done: + talloc_free(tmpctx); + return ret; +} + +/* Reset permanent negcache after checking the domains */ +errno_t sss_ncache_reset_repopulate_permanent(struct resp_ctx *rctx, + struct sss_nc_ctx *ncache) +{ + int ret; + + ret = sss_ncache_reset_permanent(ncache); + if (ret == EOK) { + ret = sss_ncache_prepopulate(ncache, rctx->cdb, rctx); + } + + return ret; +} diff --git a/src/responder/common/negcache.h b/src/responder/common/negcache.h new file mode 100644 index 0000000..29c0e6b --- /dev/null +++ b/src/responder/common/negcache.h @@ -0,0 +1,174 @@ +/* + SSSD + + NSS Responder + + Copyright (C) Simo Sorce <ssorce@redhat.com> 2008 + + 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/>. +*/ + +#ifndef _NSS_NEG_CACHE_H_ +#define _NSS_NEG_CACHE_H_ + +struct sss_nc_ctx; + +/* init the in memory negative cache */ +int sss_ncache_init(TALLOC_CTX *memctx, uint32_t timeout, + uint32_t local_timeout, struct sss_nc_ctx **_ctx); + +uint32_t sss_ncache_get_timeout(struct sss_nc_ctx *ctx); + +/* check if the user is expired according to the passed in time to live */ +int sss_ncache_check_user(struct sss_nc_ctx *ctx, struct sss_domain_info *dom, + const char *name); +int sss_ncache_check_upn(struct sss_nc_ctx *ctx, struct sss_domain_info *dom, + const char *name); +int sss_ncache_check_group(struct sss_nc_ctx *ctx, struct sss_domain_info *dom, + const char *name); +int sss_ncache_check_netgr(struct sss_nc_ctx *ctx, struct sss_domain_info *dom, + const char *name); +int sss_ncache_check_uid(struct sss_nc_ctx *ctx, struct sss_domain_info *dom, + uid_t uid); +int sss_ncache_check_gid(struct sss_nc_ctx *ctx, struct sss_domain_info *dom, + gid_t gid); +int sss_ncache_check_sid(struct sss_nc_ctx *ctx, struct sss_domain_info *dom, + const char *sid); +int sss_ncache_check_cert(struct sss_nc_ctx *ctx, const char *cert); + +int sss_ncache_check_service(struct sss_nc_ctx *ctx, + struct sss_domain_info *dom, + const char *name, + const char *proto); +int sss_ncache_check_service_port(struct sss_nc_ctx *ctx, + struct sss_domain_info *dom, + uint16_t port, + const char *proto); + +/* add a new neg-cache entry setting the timestamp to "now" unless + * "permanent" is set to true, in which case the timestamps is set to 0 + * and the negative cache never expires (used to permanently filter out + * users and groups) */ +int sss_ncache_set_user(struct sss_nc_ctx *ctx, bool permanent, + struct sss_domain_info *dom, const char *name); +int sss_ncache_set_upn(struct sss_nc_ctx *ctx, bool permanent, + struct sss_domain_info *dom, const char *name); +int sss_ncache_set_group(struct sss_nc_ctx *ctx, bool permanent, + struct sss_domain_info *dom, const char *name); +int sss_ncache_set_netgr(struct sss_nc_ctx *ctx, bool permanent, + struct sss_domain_info *dom, const char *name); +int sss_ncache_set_uid(struct sss_nc_ctx *ctx, bool permanent, + struct sss_domain_info *dom, uid_t uid); +int sss_ncache_set_gid(struct sss_nc_ctx *ctx, bool permanent, + struct sss_domain_info *dom, gid_t gid); +int sss_ncache_set_sid(struct sss_nc_ctx *ctx, bool permanent, + struct sss_domain_info *dom, const char *sid); +int sss_ncache_set_cert(struct sss_nc_ctx *ctx, bool permanent, + const char *cert); +int sss_ncache_set_service_name(struct sss_nc_ctx *ctx, bool permanent, + struct sss_domain_info *dom, + const char *name, const char *proto); +int sss_ncache_set_service_port(struct sss_nc_ctx *ctx, bool permanent, + struct sss_domain_info *dom, + uint16_t port, const char *proto); +/* + * Mark the lookup_type as not supporting the negative cache. This + * would be used by the corresponding checker to avoid needless + * subsequent calls to the locator for configurations that do not + * support the locator plugin. + * + * @param ctx The negative cache. + * @param dom The top-level domain. It is expected that the caller + * would use the top-level domain head here, because + * this negative cache is "per-request-type" which is the + * same for all subdomains of a domain. + * @param lookup_type Lookup type, e.g. getpwuid, getgrnam. + * + * @return EOK on success, errno on failure. + */ +int sss_ncache_set_domain_locate_type(struct sss_nc_ctx *ctx, + struct sss_domain_info *dom, + const char *lookup_type); +/* + * Check if the lookup_type supports the domain locator request. + * + * @param ctx The negative cache. + * @param dom The top-level domain. It is expected that the caller + * would use the top-level domain head here, because + * this negative cache is "per-request-type" which is the + * same for all subdomains of a domain. + * @param lookup_type Lookup type, e.g. getpwuid, getgrnam. + * + * @return ENOENT if the request supports the locator (or we + * haven't checked yet), EEXIST if the request does + * not support the domain locator request. + */ +int sss_ncache_check_domain_locate_type(struct sss_nc_ctx *ctx, + struct sss_domain_info *dom, + const char *key); + +/* + * Call these two functions to mark a GID as checked until the negative + * cache expires. This function is used to avoid a situation where + * GID would be found in a subsequent domain, so any request that + * searches for this GID again (even if it was cached) would first + * run the locator again. + * + * While this negative cache entry is valid, it is expected that + * the negatively cached entries in the domain's GID negative + * cache (if any) are valid. + * + * The sss_ncache_set_locate_gid() is called by the locator request + * when it finishes, the sss_ncache_check_locate_gid() is called + * by the caller of the locator request to find if the locator + * should be called at all. + */ +int sss_ncache_set_locate_gid(struct sss_nc_ctx *ctx, + struct sss_domain_info *dom, + gid_t gid); +int sss_ncache_check_locate_gid(struct sss_nc_ctx *ctx, + struct sss_domain_info *dom, + gid_t gid); +int sss_ncache_check_locate_uid(struct sss_nc_ctx *ctx, + struct sss_domain_info *dom, + uid_t uid); +int sss_ncache_set_locate_uid(struct sss_nc_ctx *ctx, + struct sss_domain_info *dom, + uid_t uid); +int sss_ncache_check_locate_sid(struct sss_nc_ctx *ctx, + struct sss_domain_info *dom, + const char *sid); +int sss_ncache_set_locate_sid(struct sss_nc_ctx *ctx, + struct sss_domain_info *dom, + const char *sid); + +int sss_ncache_reset_permanent(struct sss_nc_ctx *ctx); +/* sss_ncache_reset_[users/groups] skips permanent entries */ +int sss_ncache_reset_users(struct sss_nc_ctx *ctx); +int sss_ncache_reset_groups(struct sss_nc_ctx *ctx); + +struct resp_ctx; + +/* Set up the negative cache with values from filter_users and + * filter_groups in the sssd.conf + */ +errno_t sss_ncache_prepopulate(struct sss_nc_ctx *ncache, + struct confdb_ctx *cdb, + struct resp_ctx *rctx); + +/* Flush the negcache permament entries and then repopulate them */ +errno_t sss_ncache_reset_repopulate_permanent(struct resp_ctx *rctx, + struct sss_nc_ctx *ncache); + +#endif /* _NSS_NEG_CACHE_H_ */ diff --git a/src/responder/common/negcache_files.c b/src/responder/common/negcache_files.c new file mode 100644 index 0000000..f22796a --- /dev/null +++ b/src/responder/common/negcache_files.c @@ -0,0 +1,105 @@ +/* + SSSD + + NSS Responder + + Copyright (C) Petr Čech <pcech@redhat.com> 2016 + + 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 "util/util.h" +#include "util/nss_dl_load.h" +#include "responder/common/negcache_files.h" + +#define BUFFER_SIZE 16384 +static char s_nss_buffer[BUFFER_SIZE]; + +bool is_user_local_by_name(const struct sss_nss_ops *ops, const char *name) +{ + struct passwd pwd = { 0 }; + int errnop; + enum nss_status ret; + char *shortname = NULL; + int parse_ret; + + parse_ret = sss_parse_internal_fqname(NULL, name, &shortname, NULL); + if (parse_ret != EOK) { + return false; + } + + ret = ops->getpwnam_r(shortname, &pwd, s_nss_buffer, BUFFER_SIZE, &errnop); + talloc_free(shortname); + if (ret == NSS_STATUS_SUCCESS) { + DEBUG(SSSDBG_TRACE_FUNC, "User %s is a local user\n", name); + return true; + } + + return false; +} + +bool is_user_local_by_uid(const struct sss_nss_ops *ops, uid_t uid) +{ + struct passwd pwd = { 0 }; + int errnop; + enum nss_status ret; + + ret = ops->getpwuid_r(uid, &pwd, s_nss_buffer, BUFFER_SIZE, &errnop); + if (ret == NSS_STATUS_SUCCESS) { + DEBUG(SSSDBG_TRACE_FUNC, + "User with UID %"SPRIuid" is a local user\n", uid); + return true; + } + + return false; +} + +bool is_group_local_by_name(const struct sss_nss_ops *ops, const char *name) +{ + struct group grp = { 0 }; + int errnop; + enum nss_status ret; + char *shortname = NULL; + int parse_ret; + + parse_ret = sss_parse_internal_fqname(NULL, name, &shortname, NULL); + if (parse_ret != EOK) { + return false; + } + + ret = ops->getgrnam_r(shortname, &grp, s_nss_buffer, BUFFER_SIZE, &errnop); + talloc_free(shortname); + if (ret == NSS_STATUS_SUCCESS) { + DEBUG(SSSDBG_TRACE_FUNC, "Group %s is a local group\n", name); + return true; + } + + return false; +} + +bool is_group_local_by_gid(const struct sss_nss_ops *ops, uid_t gid) +{ + struct group grp = { 0 }; + int errnop; + enum nss_status ret; + + ret = ops->getgrgid_r(gid, &grp, s_nss_buffer, BUFFER_SIZE, &errnop); + if (ret == NSS_STATUS_SUCCESS) { + DEBUG(SSSDBG_TRACE_FUNC, + "Group with GID %"SPRIgid" is a local group\n", gid); + return true; + } + + return false; +} diff --git a/src/responder/common/negcache_files.h b/src/responder/common/negcache_files.h new file mode 100644 index 0000000..a3e18de --- /dev/null +++ b/src/responder/common/negcache_files.h @@ -0,0 +1,35 @@ +/* + SSSD + + NSS Responder + + Copyright (C) Petr Čech <pcech@redhat.com> 2016 + + 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/>. +*/ + +#ifndef _NEGCACHE_FILES_H_ +#define _NEGCACHE_FILES_H_ + +#include <stdbool.h> + +struct sss_nss_ops; + +bool is_user_local_by_name(const struct sss_nss_ops *ops, const char *name); +bool is_user_local_by_uid(const struct sss_nss_ops *ops, uid_t uid); + +bool is_group_local_by_name(const struct sss_nss_ops *ops, const char *name); +bool is_group_local_by_gid(const struct sss_nss_ops *ops, uid_t gid); + +#endif /* _NEGCACHE_FILES_H_ */ diff --git a/src/responder/common/responder.h b/src/responder/common/responder.h new file mode 100644 index 0000000..5f04d25 --- /dev/null +++ b/src/responder/common/responder.h @@ -0,0 +1,450 @@ +/* + SSSD + + SSS Client Responder, header file + + Copyright (C) Simo Sorce <ssorce@redhat.com> 2008 + + 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/>. +*/ + +#ifndef __SSS_RESPONDER_H__ +#define __SSS_RESPONDER_H__ + +#include "config.h" + +#include <stdint.h> +#include <sys/un.h> +#include <sys/resource.h> +#include <talloc.h> +#include <tevent.h> +#include <ldb.h> +#include <dhash.h> + +#include "util/sss_regexp.h" +#include "sss_iface/sss_iface_async.h" +#include "responder/common/negcache.h" +#include "sss_client/sss_cli.h" +#include "responder/common/cache_req/cache_req_domain.h" +#include "util/session_recording.h" + +extern hash_table_t *dp_requests; + +/* we want default permissions on created files to be very strict, + * so set our umask to 0177 */ +#define DFL_RSP_UMASK SSS_DFL_UMASK + +/* Public sockets must be readable and writable by anybody on the system. + * So we set umask to 0111. */ +#define SCKT_RSP_UMASK 0111 + +/* needed until nsssrv.h is updated */ +struct cli_request { + + /* original request from the wire */ + struct sss_packet *in; + + /* reply data */ + struct sss_packet *out; +}; + +struct cli_protocol_version { + uint32_t version; + const char *date; + const char *description; +}; + +struct cli_protocol { + struct cli_request *creq; + struct cli_protocol_version *cli_protocol_version; +}; + +struct resp_ctx; + +struct be_conn { + struct be_conn *next; + struct be_conn *prev; + + struct resp_ctx *rctx; + + const char *cli_name; + struct sss_domain_info *domain; + + char *bus_name; + char *sbus_address; + struct sbus_connection *conn; +}; + +struct resp_ctx { + struct tevent_context *ev; + struct tevent_fd *lfde; + int lfd; + struct tevent_fd *priv_lfde; + int priv_lfd; + struct confdb_ctx *cdb; + const char *sock_name; + const char *priv_sock_name; + + struct sss_nc_ctx *ncache; + struct sss_names_ctx *global_names; + + struct sbus_connection *mon_conn; + struct be_conn *be_conns; + + struct sss_domain_info *domains; + int domains_timeout; + int client_idle_timeout; + + struct cache_req_domain *cr_domains; + const char *domain_resolution_order; + + time_t last_request_time; + int idle_timeout; + struct tevent_timer *idle; + + struct sss_cmd_table *sss_cmds; + const char *sss_pipe_name; + const char *confdb_service_path; + + struct timeval get_domains_last_call; + + size_t allowed_uids_count; + uid_t *allowed_uids; + + char *default_domain; + char override_space; + + char **allowed_shells; + char *override_shell; + char **vetoed_shells; + char **etc_shells; + char *shell_fallback; + char *default_shell; + + struct session_recording_conf sr_conf; + + uint32_t cache_req_num; + uint32_t client_id_num; + + void *pvt_ctx; + + bool shutting_down; + bool socket_activated; + bool dbus_activated; + bool cache_first; + bool enumeration_warn_logged; +}; + +struct cli_creds; + +struct cli_ctx { + struct tevent_context *ev; + struct resp_ctx *rctx; + int cfd; + struct tevent_fd *cfde; + tevent_fd_handler_t cfd_handler; + struct sockaddr_un addr; + int priv; + + struct cli_creds *creds; + char *cmd_line; + + void *protocol_ctx; + void *state_ctx; + + struct tevent_timer *idle; + time_t last_request_time; + uint32_t client_id_num; +}; + +struct sss_cmd_table { + enum sss_cli_command cmd; + int (*fn)(struct cli_ctx *cctx); +}; + +/* from generated code */ +struct mon_cli_iface; + +/* + * responder_common.c + * + */ + +typedef int (*connection_setup_t)(struct cli_ctx *cctx); + +int sss_connection_setup(struct cli_ctx *cctx); + +void sss_client_fd_handler(void *ptr, + void (*recv_fn) (struct cli_ctx *cctx), + void (*send_fn) (struct cli_ctx *cctx), + uint16_t flags); + +int sss_process_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct confdb_ctx *cdb, + struct sss_cmd_table sss_cmds[], + const char *sss_pipe_name, + int pipe_fd, + const char *sss_priv_pipe_name, + int priv_pipe_fd, + const char *confdb_service_path, + const char *conn_name, + const char *svc_name, + connection_setup_t conn_setup, + struct resp_ctx **responder_ctx); + +int sss_dp_get_domain_conn(struct resp_ctx *rctx, const char *domain, + struct be_conn **_conn); +struct sss_domain_info * +responder_get_domain(struct resp_ctx *rctx, const char *domain); + +errno_t responder_get_domain_by_id(struct resp_ctx *rctx, const char *id, + struct sss_domain_info **_ret_dom); + +int create_pipe_fd(const char *sock_name, int *_fd, mode_t umaskval); +int activate_unix_sockets(struct resp_ctx *rctx, + connection_setup_t conn_setup); + +/* responder_cmd.c */ +int sss_cmd_empty_packet(struct sss_packet *packet); +int sss_cmd_send_empty(struct cli_ctx *cctx); +int sss_cmd_send_error(struct cli_ctx *cctx, int err); +void sss_cmd_done(struct cli_ctx *cctx, void *freectx); +int sss_cmd_get_version(struct cli_ctx *cctx); +int sss_cmd_execute(struct cli_ctx *cctx, + enum sss_cli_command cmd, + struct sss_cmd_table *sss_cmds); +struct cli_protocol_version *register_cli_protocol_version(void); + +struct setent_req_list; + +/* A facility for notifying setent requests */ +struct tevent_req *setent_get_req(struct setent_req_list *sl); +errno_t setent_add_ref(TALLOC_CTX *memctx, + struct setent_req_list **list, + struct tevent_req *req); +void setent_notify(struct setent_req_list **list, errno_t err); +void setent_notify_done(struct setent_req_list **list); + +errno_t +sss_cmd_check_cache(struct ldb_message *msg, + int cache_refresh_percent, + uint64_t cache_expire); + +void handle_requests_after_reconnect(struct resp_ctx *rctx); + +errno_t +responder_logrotate(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct resp_ctx *rctx); + +/* Send a request to the data provider + * Once this function is called, the communication + * with the data provider will always run to + * completion. Freeing the returned tevent_req will + * cancel the notification of completion, but not + * the data provider action. + */ + +enum sss_dp_acct_type { + SSS_DP_USER = 1, + SSS_DP_GROUP, + SSS_DP_INITGROUPS, + SSS_DP_SUBID_RANGES, + SSS_DP_NETGR, + SSS_DP_SERVICES, + SSS_DP_SECID, + SSS_DP_USER_AND_GROUP, + SSS_DP_CERT, + SSS_DP_WILDCARD_USER, + SSS_DP_WILDCARD_GROUP, +}; + +struct tevent_req * +sss_dp_get_account_send(TALLOC_CTX *mem_ctx, + struct resp_ctx *rctx, + struct sss_domain_info *dom, + bool fast_reply, + enum sss_dp_acct_type type, + const char *opt_name, + uint32_t opt_id, + const char *extra); +errno_t +sss_dp_get_account_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + uint16_t *_dp_error, + uint32_t *_error, + const char **_error_message); + +struct tevent_req * +sss_dp_resolver_get_send(TALLOC_CTX *mem_ctx, + struct resp_ctx *rctx, + struct sss_domain_info *dom, + bool fast_reply, + uint32_t entry_type, + uint32_t filter_type, + const char *filter_value); + +errno_t +sss_dp_resolver_get_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + uint16_t *_dp_error, + uint32_t *_error, + const char **_error_message); + +bool sss_utf8_check(const uint8_t *s, size_t n); + +void responder_set_fd_limit(rlim_t fd_limit); + +errno_t reset_client_idle_timer(struct cli_ctx *cctx); + +errno_t responder_setup_idle_timeout_config(struct resp_ctx *rctx); + +#define GET_DOMAINS_DEFAULT_TIMEOUT 60 + +struct tevent_req *sss_dp_get_domains_send(TALLOC_CTX *mem_ctx, + struct resp_ctx *rctx, + bool force, + const char *hint); + +errno_t sss_dp_get_domains_recv(struct tevent_req *req); + +/* + * Call a getAccountDomain request + * + * Only requests by ID are supported. + * + * @param mem_ctx Parent memory context + * @param rctx Responder context + * @param domain The SSSD domain we're querying. The response can + * be either NULL or come from any of domain's subdomains + * or domain itself + * @param type Either SSS_DP_USER, SSS_DP_GROUP or SSS_DP_SECID, other + * types are not supported at the moment + * @param opt_id The ID number we're trying to locate + * @param opt_str The SID number we're trying to locate + * + * @return A tevent request or NULL if allocating the request fails. + */ +struct tevent_req *sss_dp_get_account_domain_send(TALLOC_CTX *mem_ctx, + struct resp_ctx *rctx, + struct sss_domain_info *domain, + bool fast_reply, + enum sss_dp_acct_type type, + uint32_t opt_id, + const char *opt_str); + +/* Receive a getAccountDomain request result + * + * @param mem_ctx The memory context that will own the contents of _domain + * @param req The request that had finished + * @para _domain Either NULL (the request did not match any domain) or + * a string that corresponds to either the input domain + * or any of its subdomains + * + * @return EOK on success, errno otherwise + */ +errno_t sss_dp_get_account_domain_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char **_domain); + +typedef void (get_domains_callback_fn_t)(void *); +errno_t schedule_get_domains_task(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *optional_ncache, + get_domains_callback_fn_t *callback, + void *callback_pvt); + +errno_t csv_string_to_uid_array(TALLOC_CTX *mem_ctx, const char *csv_string, + size_t *_uid_count, uid_t **_uids); + +uid_t client_euid(struct cli_creds *creds); +errno_t check_allowed_uids(uid_t uid, size_t allowed_uids_count, + const uid_t *allowed_uids); + +struct tevent_req * +sss_parse_inp_send(TALLOC_CTX *mem_ctx, + struct resp_ctx *rctx, + const char *default_domain, + const char *rawinp); + +errno_t sss_parse_inp_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + char **_name, char **_domname); + +const char **parse_attr_list_ex(TALLOC_CTX *mem_ctx, const char *conf_str, + const char **defaults); + +char *sss_resp_create_fqname(TALLOC_CTX *mem_ctx, + struct resp_ctx *rctx, + struct sss_domain_info *dom, + bool name_is_upn, + const char *orig_name); + +errno_t sss_resp_populate_cr_domains(struct resp_ctx *rctx); + +const char * +sss_resp_get_shell_override(struct ldb_message *msg, + struct resp_ctx *rctx, + struct sss_domain_info *domain); + +/** + * Helper functions to format output names + */ + +/* Format orig_name into a sized_string in output format as prescribed + * by the name_dom domain + */ +int sized_output_name(TALLOC_CTX *mem_ctx, + struct resp_ctx *rctx, + const char *orig_name, + struct sss_domain_info *name_dom, + struct sized_string **_name); + +/* Format orig_name into a sized_string in output format as prescribed + * by the domain read from the fully qualified name. + */ +int sized_domain_name(TALLOC_CTX *mem_ctx, + struct resp_ctx *rctx, + const char *member_name, + struct sized_string **_name); + +/* Given a ldb_result structure that contains a result of sysdb_initgroups + * where some groups might be just 'stubs' that don't have a name, but only + * a SID and a GID, resolve those incomplete groups into full group objects + */ +struct tevent_req *resp_resolve_group_names_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_domain_info *dom, + struct ldb_result *initgr_res); + +int resp_resolve_group_names_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ldb_result **_initgr_named_res); + +/** + * Register common responder sbus interface on connection. + */ +errno_t +sss_resp_register_sbus_iface(struct sbus_connection *conn, + struct resp_ctx *rctx); + +/** + * Register common service sbus interface on monitor connection. + */ +errno_t +sss_resp_register_service_iface(struct resp_ctx *rctx); + +#endif /* __SSS_RESPONDER_H__ */ diff --git a/src/responder/common/responder_cmd.c b/src/responder/common/responder_cmd.c new file mode 100644 index 0000000..bd05ca2 --- /dev/null +++ b/src/responder/common/responder_cmd.c @@ -0,0 +1,302 @@ +/* + SSSD + + SSS Client Responder, command parser + + Copyright (C) Simo Sorce <ssorce@redhat.com> 2008 + + 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 "db/sysdb.h" +#include "util/util.h" +#include "responder/common/responder.h" +#include "responder/common/responder_packet.h" + + +int sss_cmd_send_error(struct cli_ctx *cctx, int err) +{ + struct cli_protocol *pctx; + int ret; + + pctx = talloc_get_type(cctx->protocol_ctx, struct cli_protocol); + if (!pctx) return EINVAL; + + /* create response packet */ + ret = sss_packet_new(pctx->creq, 0, + sss_packet_get_cmd(pctx->creq->in), + &pctx->creq->out); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot create new packet: %d\n", ret); + return ret; + } + + sss_packet_set_error(pctx->creq->out, err); + return EOK; +} + +int sss_cmd_empty_packet(struct sss_packet *packet) +{ + uint8_t *body; + size_t blen; + int ret; + + ret = sss_packet_grow(packet, 2*sizeof(uint32_t)); + if (ret != EOK) return ret; + + sss_packet_get_body(packet, &body, &blen); + + /* num results */ + SAFEALIGN_SETMEM_UINT32(body, 0, NULL); + + /* reserved */ + SAFEALIGN_SETMEM_UINT32(body + sizeof(uint32_t), 0, NULL); + + return EOK; +} + +int sss_cmd_send_empty(struct cli_ctx *cctx) +{ + struct cli_protocol *pctx; + int ret; + + pctx = talloc_get_type(cctx->protocol_ctx, struct cli_protocol); + if (!pctx) return EINVAL; + + /* create response packet */ + ret = sss_packet_new(pctx->creq, 0, + sss_packet_get_cmd(pctx->creq->in), + &pctx->creq->out); + if (ret != EOK) { + return ret; + } + + ret = sss_cmd_empty_packet(pctx->creq->out); + if (ret != EOK) { + return ret; + } + + sss_packet_set_error(pctx->creq->out, EOK); + return EOK; +} + +void sss_cmd_done(struct cli_ctx *cctx, void *freectx) +{ + /* now that the packet is in place, unlock queue + * making the event writable */ + TEVENT_FD_WRITEABLE(cctx->cfde); + + /* free all request related data through the talloc hierarchy */ + talloc_free(freectx); +} + +int sss_cmd_get_version(struct cli_ctx *cctx) +{ + struct cli_protocol *pctx; + uint8_t *req_body; + size_t req_blen; + uint8_t *body; + size_t blen; + int ret; + uint32_t client_version; + uint32_t protocol_version; + int i; + static struct cli_protocol_version *cli_protocol_version = NULL; + + pctx = talloc_get_type(cctx->protocol_ctx, struct cli_protocol); + if (!pctx) return EINVAL; + + pctx->cli_protocol_version = NULL; + + if (cli_protocol_version == NULL) { + cli_protocol_version = register_cli_protocol_version(); + } + + if (cli_protocol_version != NULL) { + pctx->cli_protocol_version = &cli_protocol_version[0]; + + sss_packet_get_body(pctx->creq->in, &req_body, &req_blen); + if (req_blen == sizeof(uint32_t)) { + memcpy(&client_version, req_body, sizeof(uint32_t)); + DEBUG(SSSDBG_FUNC_DATA, + "Received client version [%d].\n", client_version); + + i=0; + while(cli_protocol_version[i].version>0) { + if (cli_protocol_version[i].version == client_version) { + pctx->cli_protocol_version = &cli_protocol_version[i]; + break; + } + i++; + } + } + } + + /* create response packet */ + ret = sss_packet_new(pctx->creq, sizeof(uint32_t), + sss_packet_get_cmd(pctx->creq->in), + &pctx->creq->out); + if (ret != EOK) { + return ret; + } + sss_packet_get_body(pctx->creq->out, &body, &blen); + + protocol_version = (pctx->cli_protocol_version != NULL) + ? pctx->cli_protocol_version->version : 0; + + SAFEALIGN_COPY_UINT32(body, &protocol_version, NULL); + DEBUG(SSSDBG_FUNC_DATA, "Offered version [%d].\n", protocol_version); + + sss_cmd_done(cctx, NULL); + return EOK; +} + +int sss_cmd_execute(struct cli_ctx *cctx, + enum sss_cli_command cmd, + struct sss_cmd_table *sss_cmds) +{ + int i; + + for (i = 0; sss_cmds[i].cmd != SSS_CLI_NULL; i++) { + if (cmd == sss_cmds[i].cmd) { + return sss_cmds[i].fn(cctx); + } + } + + return EINVAL; +} +struct setent_req_list { + struct setent_req_list *prev; + struct setent_req_list *next; + /* Need to modify the list from a talloc destructor */ + struct setent_req_list **head; + + struct tevent_req *req; +}; + +struct tevent_req * +setent_get_req(struct setent_req_list *sl) +{ + return sl->req; +} + +int setent_remove_ref(TALLOC_CTX *ctx) +{ + struct setent_req_list *entry = + talloc_get_type(ctx, struct setent_req_list); + DLIST_REMOVE(*(entry->head), entry); + return 0; +} + +errno_t setent_add_ref(TALLOC_CTX *memctx, + struct setent_req_list **list, + struct tevent_req *req) +{ + struct setent_req_list *entry; + + entry = talloc_zero(memctx, struct setent_req_list); + if (!entry) { + return ENOMEM; + } + + entry->req = req; + DLIST_ADD_END(*list, entry, struct setent_req_list *); + entry->head = list; + + talloc_set_destructor((TALLOC_CTX *)entry, setent_remove_ref); + return EOK; +} + +void setent_notify(struct setent_req_list **list, errno_t err) +{ + struct setent_req_list *reql; + + /* Notify the waiting clients */ + while ((reql = *list) != NULL) { + /* Each tevent_req_done() call will free + * the request, removing it from the list. + */ + if (err == EOK) { + tevent_req_done(reql->req); + } else { + tevent_req_error(reql->req, err); + } + + if (reql == *list) { + /* The consumer failed to free the + * request. Log a bug and continue. + */ + DEBUG(SSSDBG_FATAL_FAILURE, + "BUG: a callback did not free its request. " + "May leak memory\n"); + /* Skip to the next since a memory leak is non-fatal */ + *list = (*list)->next; + } + } +} + +void setent_notify_done(struct setent_req_list **list) +{ + return setent_notify(list, EOK); +} + +/* + * Return values: + * EOK - cache hit + * EAGAIN - cache hit, but schedule off band update + * ENOENT - cache miss + */ +errno_t +sss_cmd_check_cache(struct ldb_message *msg, + int cache_refresh_percent, + uint64_t cache_expire) +{ + uint64_t lastUpdate; + uint64_t midpoint_refresh = 0; + time_t now; + + now = time(NULL); + lastUpdate = ldb_msg_find_attr_as_uint64(msg, SYSDB_LAST_UPDATE, 0); + midpoint_refresh = 0; + + if(cache_refresh_percent) { + midpoint_refresh = lastUpdate + + (cache_expire - lastUpdate)*cache_refresh_percent/100.0; + if (midpoint_refresh - lastUpdate < 10) { + /* If the percentage results in an expiration + * less than ten seconds after the lastUpdate time, + * that's too often we will simply set it to 10s + */ + midpoint_refresh = lastUpdate+10; + } + } + + if (cache_expire > now) { + /* cache still valid */ + + if (midpoint_refresh && midpoint_refresh < now) { + /* We're past the cache refresh timeout + * We'll return the value from the cache, but we'll also + * queue the cache entry for update out-of-band. + */ + return EAGAIN; + } else { + /* Cache is still valid. */ + return EOK; + } + } + + /* Cache needs to be updated */ + return ENOENT; +} diff --git a/src/responder/common/responder_common.c b/src/responder/common/responder_common.c new file mode 100644 index 0000000..ac0e727 --- /dev/null +++ b/src/responder/common/responder_common.c @@ -0,0 +1,2001 @@ +/* + SSSD + + Common Responder methods + + Copyright (C) Simo Sorce <ssorce@redhat.com> 2008 + + 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 "config.h" + +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <string.h> +#include <sys/time.h> +#include <errno.h> +#include <fcntl.h> +#include <popt.h> +#include <dbus/dbus.h> + +#include "util/util.h" +#include "util/strtonum.h" +#include "db/sysdb.h" +#include "confdb/confdb.h" +#include "responder/common/responder.h" +#include "responder/common/responder_packet.h" +#include "providers/data_provider.h" +#include "util/util_creds.h" +#include "sss_iface/sss_iface_async.h" +#include "util/sss_chain_id_tevent.h" +#include "util/sss_chain_id.h" + +#ifdef HAVE_SYSTEMD +#include <systemd/sd-daemon.h> +#endif + +#define SHELL_REALLOC_INCREMENT 5 +#define SHELL_REALLOC_MAX 50 + +static errno_t set_close_on_exec(int fd) +{ + int v; + int ferr; + errno_t error; + + /* Get the current flags for this file descriptor */ + v = fcntl(fd, F_GETFD, 0); + + errno = 0; + /* Set the close-on-exec flags on this fd */ + ferr = fcntl(fd, F_SETFD, v | FD_CLOEXEC); + if (ferr < 0) { + error = errno; + DEBUG(SSSDBG_FATAL_FAILURE, + "Unable to set fd close-on-exec: [%d][%s]\n", + error, strerror(error)); + return error; + } + return EOK; +} + +static void client_close_fn(struct tevent_context *ev, + struct tevent_fd *fde, int fd, + void *ptr) +{ + errno_t ret; + struct cli_ctx *ctx = talloc_get_type(ptr, struct cli_ctx); + + if ((ctx->cfd > 0) && close(ctx->cfd) < 0) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to close fd [%d]: [%s]\n", + ctx->cfd, strerror(ret)); + } + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Terminated client [%p][%d]\n", + ctx, ctx->cfd); + + ctx->cfd = -1; +} + +static errno_t get_client_cred(struct cli_ctx *cctx) +{ + SEC_CTX secctx; + int ret; + + cctx->creds = talloc_zero(cctx, struct cli_creds); + if (!cctx->creds) return ENOMEM; + +#ifdef HAVE_UCRED + socklen_t client_cred_len = sizeof(struct ucred); + char proc_path[32]; + char cmd_line[255] = { 0 }; + int proc_fd; + + cctx->creds->ucred.uid = -1; + cctx->creds->ucred.gid = -1; + cctx->creds->ucred.pid = -1; + + ret = getsockopt(cctx->cfd, SOL_SOCKET, SO_PEERCRED, &cctx->creds->ucred, + &client_cred_len); + if (ret != EOK) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "getsockopt failed [%d][%s].\n", ret, strerror(ret)); + return ret; + } + if (client_cred_len != sizeof(struct ucred)) { + DEBUG(SSSDBG_CRIT_FAILURE, + "getsockopt returned unexpected message size.\n"); + return ENOMSG; + } + + if (cctx->creds->ucred.pid > -1) { + snprintf(proc_path, sizeof(proc_path), "/proc/%d/cmdline", + (int)cctx->creds->ucred.pid); + proc_fd = open(proc_path, O_RDONLY); + if (proc_fd != -1) { + if (sss_fd_nonblocking(proc_fd) == EOK) { + ret = read(proc_fd, cmd_line, sizeof(cmd_line)-1); + if (ret > 0) { + cmd_line[ret] = 0; + cctx->cmd_line = talloc_strdup(cctx, cmd_line); + } + } + close(proc_fd); + } + } + + DEBUG(SSSDBG_TRACE_ALL, + "Client [%p][%d] creds: euid[%d] egid[%d] pid[%d] cmd_line['%s'].\n", + cctx, cctx->cfd, + cctx->creds->ucred.uid, cctx->creds->ucred.gid, + cctx->creds->ucred.pid, cmd_line); +#endif + + ret = SELINUX_getpeercon(cctx->cfd, &secctx); + if (ret != 0) { + ret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, + "The following failure is expected to happen in case SELinux is disabled:\n" + "SELINUX_getpeercon failed [%d][%s].\n" + "Please, consider enabling SELinux in your system.\n", ret, strerror(ret)); + /* This is not fatal, as SELinux may simply be disabled */ + ret = EOK; + } else { + cctx->creds->selinux_ctx = SELINUX_context_new(secctx); + SELINUX_freecon(secctx); + } + + return ret; +} + +uid_t client_euid(struct cli_creds *creds) +{ + if (!creds) return -1; + return cli_creds_get_uid(creds); +} + +errno_t check_allowed_uids(uid_t uid, size_t allowed_uids_count, + const uid_t *allowed_uids) +{ + size_t c; + + if (allowed_uids == NULL) { + return EINVAL; + } + + for (c = 0; c < allowed_uids_count; c++) { + if (uid == allowed_uids[c]) { + return EOK; + } + } + + return EACCES; +} + +errno_t csv_string_to_uid_array(TALLOC_CTX *mem_ctx, const char *csv_string, + size_t *_uid_count, uid_t **_uids) +{ + int ret; + size_t c; + char **list = NULL; + int list_size; + uid_t *uids = NULL; + char *endptr; + const char *envvar; + bool loops_were_allowed; + + envvar = getenv("_SSS_LOOPS"); + loops_were_allowed = (envvar == NULL || strcmp(envvar, "NO") != 0); + + if (!loops_were_allowed) { + ret = unsetenv("_SSS_LOOPS"); + if (ret != 0) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to unset _SSS_LOOPS.\n"); + goto done; + } + } + + ret = split_on_separator(mem_ctx, csv_string, ',', true, false, + &list, &list_size); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "split_on_separator failed [%d][%s].\n", + ret, strerror(ret)); + goto done; + } + + uids = talloc_array(mem_ctx, uint32_t, list_size); + if (uids == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_array failed.\n"); + ret = ENOMEM; + goto done; + } + + for (c = 0; c < list_size; c++) { + if (*list[c] == '\0') { + DEBUG(SSSDBG_OP_FAILURE, "Empty list item.\n"); + ret = EINVAL; + goto done; + } + + uids[c] = strtouint32(list[c], &endptr, 10); + if ((errno != 0) || (*endptr != '\0') || (list[c] == endptr)) { + ret = errno; + if (ret == ERANGE) { + DEBUG(SSSDBG_OP_FAILURE, "List item [%s] is out of range.\n", + list[c]); + goto done; + } + + ret = sss_user_by_name_or_uid(list[c], &uids[c], NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "List item [%s] is neither a valid " + "UID nor a user name which could be " + "resolved by getpwnam().\n", list[c]); + sss_log(SSS_LOG_WARNING, "List item [%s] is neither a valid " + "UID nor a user name which could be " + "resolved by getpwnam().\n", list[c]); + goto done; + } + } + } + + *_uid_count = list_size; + *_uids = uids; + + ret = EOK; + +done: + if (!loops_were_allowed) { + if (setenv("_SSS_LOOPS", "NO" , 0) != 0) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to restore _SSS_LOOPS.\n"); + } + } + talloc_free(list); + if (ret != EOK) { + talloc_free(uids); + } + + return ret; +} + +static void client_send(struct cli_ctx *cctx) +{ + struct cli_protocol *pctx; + int ret; + + pctx = talloc_get_type(cctx->protocol_ctx, struct cli_protocol); + + ret = sss_packet_send(pctx->creq->out, cctx->cfd); + if (ret == EAGAIN) { + /* not all data was sent, loop again */ + return; + } + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to send data, aborting client!\n"); + talloc_free(cctx); + return; + } + + /* ok all sent */ + TEVENT_FD_NOT_WRITEABLE(cctx->cfde); + TEVENT_FD_READABLE(cctx->cfde); + talloc_zfree(pctx->creq); + return; +} + +static int client_cmd_execute(struct cli_ctx *cctx, struct sss_cmd_table *sss_cmds) +{ + struct cli_protocol *pctx; + enum sss_cli_command cmd; + + pctx = talloc_get_type(cctx->protocol_ctx, struct cli_protocol); + cmd = sss_packet_get_cmd(pctx->creq->in); + return sss_cmd_execute(cctx, cmd, sss_cmds); +} + +static void client_recv(struct cli_ctx *cctx) +{ + struct cli_protocol *pctx; + int ret; + + pctx = talloc_get_type(cctx->protocol_ctx, struct cli_protocol); + + if (!pctx->creq) { + pctx->creq = talloc_zero(cctx, struct cli_request); + if (!pctx->creq) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to alloc request, aborting client!\n"); + talloc_free(cctx); + return; + } + } + + if (!pctx->creq->in) { + ret = sss_packet_new(pctx->creq, SSS_PACKET_MAX_RECV_SIZE, + 0, &pctx->creq->in); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to alloc request, aborting client!\n"); + talloc_free(cctx); + return; + } + } + + ret = sss_packet_recv(pctx->creq->in, cctx->cfd); + switch (ret) { + case EOK: + /* do not read anymore */ + TEVENT_FD_NOT_READABLE(cctx->cfde); + /* execute command */ + ret = client_cmd_execute(cctx, cctx->rctx->sss_cmds); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to execute request, aborting client!\n"); + talloc_free(cctx); + } + /* past this point cctx can be freed at any time by callbacks + * in case of error, do not use it */ + return; + + case EAGAIN: + /* need to read still some data, loop again */ + break; + + case EINVAL: + DEBUG(SSSDBG_TRACE_FUNC, + "Invalid data from client, closing connection!\n"); + talloc_free(cctx); + break; + + case ENODATA: + DEBUG(SSSDBG_FUNC_DATA, "Client disconnected!\n"); + talloc_free(cctx); + break; + + default: + DEBUG(SSSDBG_TRACE_FUNC, "Failed to read request, aborting client!\n"); + talloc_free(cctx); + } + + return; +} + +static errno_t schedule_responder_idle_timer(struct resp_ctx *rctx); + +static void responder_idle_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *data) +{ + struct resp_ctx *rctx; + time_t now; + + rctx = talloc_get_type(data, struct resp_ctx); + + now = time(NULL); + if (rctx->last_request_time > now) { + DEBUG(SSSDBG_IMPORTANT_INFO, + "Time shift detected, re-scheduling the responder timeout\n"); + goto end; + } + + if ((now - rctx->last_request_time) >= rctx->idle_timeout) { + /* This responder is idle. Terminate it */ + DEBUG(SSSDBG_TRACE_INTERNAL, + "Terminating idle responder [%p]\n", rctx); + + talloc_free(rctx); + + orderly_shutdown(0); + } + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Re-scheduling the idle timeout [%s] for the responder [%p]\n", + CONFDB_RESPONDER_IDLE_TIMEOUT, rctx); + +end: + schedule_responder_idle_timer(rctx); +} + +static errno_t schedule_responder_idle_timer(struct resp_ctx *rctx) +{ + struct timeval tv; + + tv = tevent_timeval_current_ofs(rctx->idle_timeout / 2, 0); + + talloc_zfree(rctx->idle); + rctx->idle = tevent_add_timer(rctx->ev, + rctx, + tv, + responder_idle_handler, + rctx); + if (rctx->idle == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to allocate time event: responder [%p] shutdown timeout\n", + rctx); + return ENOMEM; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Re-scheduling the idle timeout [%s] for the responder [%p]\n", + CONFDB_RESPONDER_IDLE_TIMEOUT, rctx); + + return EOK; +} + +static errno_t setup_responder_idle_timer(struct resp_ctx *rctx) +{ + errno_t ret; + + rctx->last_request_time = time(NULL); + + ret = schedule_responder_idle_timer(rctx); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Error scheduling the idle timeout [%s] for the responder [%p]: " + "%d [%s]\n", + CONFDB_RESPONDER_IDLE_TIMEOUT, rctx, ret, sss_strerror(ret)); + return ret; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Setting up the idle timeout [%s] for the responder [%p]\n", + CONFDB_RESPONDER_IDLE_TIMEOUT, rctx); + + return EOK; +} + +static void client_fd_handler(struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, void *ptr) +{ + sss_client_fd_handler(ptr, client_recv, client_send, flags); +} + +static errno_t setup_client_idle_timer(struct cli_ctx *cctx); + +static int cli_ctx_destructor(struct cli_ctx *cctx) +{ + if (cctx->creds == NULL) { + return 0; + } + + if (cctx->creds->selinux_ctx == NULL) { + return 0; + } + + SELINUX_context_free(cctx->creds->selinux_ctx); + cctx->creds->selinux_ctx = NULL; + + return 0; +} + +struct accept_fd_ctx { + struct resp_ctx *rctx; + bool is_private; + connection_setup_t connection_setup; +}; + +/* + * Use this function only before the client context is established + */ +static void accept_and_terminate_cli(int fd) +{ + struct sockaddr_un addr; + int client_fd; + socklen_t len; + + /* accept and close to signal the client we have a problem */ + memset(&addr, 0, sizeof(addr)); + len = sizeof(addr); + client_fd = accept(fd, (struct sockaddr *)&addr, &len); + if (client_fd == -1) { + return; + } + close(client_fd); + return; +} + +static void accept_fd_handler(struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, void *ptr) +{ + static uid_t last_violator_uid = (uid_t)-1; + /* accept and attach new event handler */ + struct accept_fd_ctx *accept_ctx = + talloc_get_type(ptr, struct accept_fd_ctx); + struct resp_ctx *rctx = accept_ctx->rctx; + struct cli_ctx *cctx; + socklen_t len; + struct stat stat_buf; + int ret; + int fd = accept_ctx->is_private ? rctx->priv_lfd : rctx->lfd; + + rctx->client_id_num++; + if (accept_ctx->is_private) { + ret = stat(rctx->priv_sock_name, &stat_buf); + if (ret == -1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "stat on privileged pipe failed: [%d][%s].\n", + errno, strerror(errno)); + accept_and_terminate_cli(fd); + return; + } + + if ( ! (stat_buf.st_uid == 0 && stat_buf.st_gid == 0 && + (stat_buf.st_mode&(S_IFSOCK|S_IRUSR|S_IWUSR)) == stat_buf.st_mode)) { + DEBUG(SSSDBG_CRIT_FAILURE, + "privileged pipe has an illegal status.\n"); + accept_and_terminate_cli(fd); + return; + } + } + + cctx = talloc_zero(rctx, struct cli_ctx); + if (!cctx) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Out of memory trying to setup client context%s!\n", + accept_ctx->is_private ? " on privileged pipe": ""); + accept_and_terminate_cli(fd); + return; + } + + talloc_set_destructor(cctx, cli_ctx_destructor); + + cctx->client_id_num = rctx->client_id_num; + + len = sizeof(cctx->addr); + cctx->cfd = accept(fd, (struct sockaddr *)&cctx->addr, &len); + if (cctx->cfd == -1) { + DEBUG(SSSDBG_CRIT_FAILURE, "Accept failed [%s]\n", strerror(errno)); + talloc_free(cctx); + return; + } + + cctx->priv = accept_ctx->is_private; + + ret = get_client_cred(cctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_client_cred failed, " + "client cred may not be available.\n"); + } + + if (rctx->allowed_uids_count != 0) { + if (client_euid(cctx->creds) == -1) { + DEBUG(SSSDBG_CRIT_FAILURE, "allowed_uids configured, " \ + "but platform does not support " \ + "reading peer credential from the " \ + "socket. Access denied.\n"); + close(cctx->cfd); + talloc_free(cctx); + return; + } + + ret = check_allowed_uids(client_euid(cctx->creds), rctx->allowed_uids_count, + rctx->allowed_uids); + if (ret != EOK) { + if (ret == EACCES) { + if (client_euid(cctx->creds) != last_violator_uid) { + last_violator_uid = client_euid(cctx->creds); + DEBUG(SSSDBG_IMPORTANT_INFO, + "Access denied for uid [%"SPRIuid"].\n", + last_violator_uid); + } + } else { + DEBUG(SSSDBG_OP_FAILURE, "check_allowed_uids failed.\n"); + } + close(cctx->cfd); + talloc_free(cctx); + return; + } + } + + ret = accept_ctx->connection_setup(cctx); + if (ret != EOK) { + close(cctx->cfd); + talloc_free(cctx); + DEBUG(SSSDBG_OP_FAILURE, + "Failed to setup client handler%s\n", + accept_ctx->is_private ? " on privileged pipe" : ""); + return; + } + + cctx->cfde = tevent_add_fd(ev, cctx, cctx->cfd, + TEVENT_FD_READ, cctx->cfd_handler, + cctx); + if (!cctx->cfde) { + close(cctx->cfd); + talloc_free(cctx); + DEBUG(SSSDBG_OP_FAILURE, + "Failed to queue client handler%s\n", + accept_ctx->is_private ? " on privileged pipe" : ""); + return; + } + tevent_fd_set_close_fn(cctx->cfde, client_close_fn); + + cctx->ev = ev; + cctx->rctx = rctx; + + /* Record the new time and set up the idle timer */ + ret = reset_client_idle_timer(cctx); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not create idle timer for client. " + "This connection may not auto-terminate\n"); + /* Non-fatal, continue */ + } + + ret = setup_client_idle_timer(cctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not create idle timer for client. " + "This connection may not auto-terminate\n"); + /* Non-fatal, continue */ + } + + DEBUG(SSSDBG_TRACE_FUNC, + "[CID#%u] Client [cmd %s][uid %u][%p][%d] connected%s!\n", + cctx->client_id_num, cctx->cmd_line, client_euid(cctx->creds), + cctx, cctx->cfd, accept_ctx->is_private ? " to privileged pipe" : ""); + + return; +} + +static void client_idle_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *data) +{ + time_t now = time(NULL); + struct cli_ctx *cctx = talloc_get_type(data, struct cli_ctx); + + if (cctx->last_request_time > now) { + DEBUG(SSSDBG_IMPORTANT_INFO, + "Time shift detected, re-scheduling the client timeout [%s].\n", + CONFDB_RESPONDER_CLI_IDLE_TIMEOUT); + goto done; + } + + if ((now - cctx->last_request_time) > cctx->rctx->client_idle_timeout) { + /* This connection is idle. Terminate it */ + DEBUG(SSSDBG_TRACE_INTERNAL, + "Terminating idle client [%p][%d]\n", + cctx, cctx->cfd); + + /* The cli_ctx destructor will handle the rest */ + talloc_free(cctx); + return; + } + +done: + setup_client_idle_timer(cctx); +} + +errno_t reset_client_idle_timer(struct cli_ctx *cctx) +{ + cctx->last_request_time = time(NULL); + + return EOK; +} + +static errno_t setup_client_idle_timer(struct cli_ctx *cctx) +{ + struct timeval tv = + tevent_timeval_current_ofs(cctx->rctx->client_idle_timeout/2, 0); + + talloc_zfree(cctx->idle); + + cctx->idle = tevent_add_timer(cctx->ev, cctx, tv, client_idle_handler, cctx); + if (!cctx->idle) return ENOMEM; + + DEBUG(SSSDBG_TRACE_ALL, + "Idle timer re-set for client [%p][%d]\n", + cctx, cctx->cfd); + + return EOK; +} + +static void +sss_dp_on_reconnect(struct sbus_connection *conn, + enum sbus_reconnect_status status, + struct be_conn *be_conn); + +static void +sss_dp_init_done(struct tevent_req *req); + +static errno_t +sss_dp_init(struct resp_ctx *rctx, + const char *conn_name, + const char *cli_name, + struct sss_domain_info *domain) +{ + struct tevent_req *req; + struct be_conn *be_conn; + int max_retries; + errno_t ret; + + ret = confdb_get_int(rctx->cdb, rctx->confdb_service_path, + CONFDB_SERVICE_RECON_RETRIES, 3, &max_retries); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to read confdb [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + be_conn = talloc_zero(rctx, struct be_conn); + if (!be_conn) return ENOMEM; + + be_conn->cli_name = cli_name; + be_conn->domain = domain; + be_conn->rctx = rctx; + + be_conn->sbus_address = sss_iface_domain_address(be_conn, domain); + if (be_conn->sbus_address == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not locate DP address.\n"); + ret = ENOMEM; + goto done; + } + + be_conn->bus_name = sss_iface_domain_bus(be_conn, domain); + if (be_conn->bus_name == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not locate DP address.\n"); + ret = ENOMEM; + goto done; + } + + ret = sss_iface_connect_address(be_conn, rctx->ev, conn_name, + be_conn->sbus_address, NULL, + &be_conn->conn); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to connect to backend server.\n"); + goto done; + } + + ret = sss_resp_register_sbus_iface(be_conn->conn, rctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Cannot register generic responder " + "interface [%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + sbus_reconnect_enable(be_conn->conn, max_retries, sss_dp_on_reconnect, + be_conn); + + DLIST_ADD_END(rctx->be_conns, be_conn, struct be_conn *); + + /* Identify ourselves to the DP */ + req = sbus_call_dp_client_Register_send(be_conn, be_conn->conn, + be_conn->bus_name, + SSS_BUS_PATH, cli_name); + if (req == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(req, sss_dp_init_done, be_conn); + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(be_conn); + } + + return ret; +} + +static void +sss_dp_on_reconnect(struct sbus_connection *conn, + enum sbus_reconnect_status status, + struct be_conn *be_conn) +{ + struct tevent_req *req; + + if (status != SBUS_RECONNECT_SUCCESS) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not reconnect to %s provider.\n", + be_conn->domain->name); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Reconnected to the Data Provider.\n"); + + /* Identify ourselves to the DP */ + req = sbus_call_dp_client_Register_send(be_conn, be_conn->conn, + be_conn->bus_name, + SSS_BUS_PATH, + be_conn->cli_name); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sbus_call_dp_client_Register_send() failed\n"); + return; + } + + tevent_req_set_callback(req, sss_dp_init_done, be_conn); +} + +static void +sss_dp_init_done(struct tevent_req *req) +{ + errno_t ret; + + ret = sbus_call_dp_client_Register_recv(req); + talloc_zfree(req); + + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to register client with DP\n"); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Client is registered with DP\n"); +} + +int create_pipe_fd(const char *sock_name, int *_fd, mode_t umaskval) +{ + struct sockaddr_un addr; + mode_t orig_umaskval; + errno_t ret; + int fd; + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd == -1) { + return EIO; + } + + orig_umaskval = umask(umaskval); + + ret = sss_fd_nonblocking(fd); + if (ret != EOK) { + goto done; + } + + ret = set_close_on_exec(fd); + if (ret != EOK) { + goto done; + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, sock_name, sizeof(addr.sun_path) - 1); + addr.sun_path[sizeof(addr.sun_path) - 1] = '\0'; + + /* make sure we have no old sockets around */ + ret = unlink(sock_name); + if (ret != 0 && errno != ENOENT) { + ret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot remove old socket (errno=%d [%s]), bind might fail!\n", + ret, sss_strerror(ret)); + } + + if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) { + ret = errno; + DEBUG(SSSDBG_FATAL_FAILURE, + "Unable to bind on socket '%s' [%d]: %s\n", + sock_name, ret, sss_strerror(ret)); + goto done; + } + + if (listen(fd, 128) == -1) { + ret = errno; + DEBUG(SSSDBG_FATAL_FAILURE, + "Unable to listen on socket '%s' [%d]: %s\n", + sock_name, ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; + +done: + /* restore previous umask value */ + umask(orig_umaskval); + if (ret == EOK) { + *_fd = fd; + } else { + close(fd); + } + return ret; +} + +/* create a unix socket and listen to it */ +static int set_unix_socket(struct resp_ctx *rctx, + connection_setup_t conn_setup) +{ + errno_t ret; + struct accept_fd_ctx *accept_ctx = NULL; + +/* for future use */ +#if 0 + char *default_pipe; + int ret; + + default_pipe = talloc_asprintf(rctx, "%s/%s", PIPE_PATH, + rctx->sss_pipe_name); + if (!default_pipe) { + return ENOMEM; + } + + ret = confdb_get_string(rctx->cdb, rctx, + rctx->confdb_socket_path, "unixSocket", + default_pipe, &rctx->sock_name); + if (ret != EOK) { + talloc_free(default_pipe); + return ret; + } + talloc_free(default_pipe); + + default_pipe = talloc_asprintf(rctx, "%s/private/%s", PIPE_PATH, + rctx->sss_pipe_name); + if (!default_pipe) { + return ENOMEM; + } + + ret = confdb_get_string(rctx->cdb, rctx, + rctx->confdb_socket_path, "privUnixSocket", + default_pipe, &rctx->priv_sock_name); + if (ret != EOK) { + talloc_free(default_pipe); + return ret; + } + talloc_free(default_pipe); +#endif + + if (rctx->sock_name != NULL ) { + /* Set the umask so that permissions are set right on the socket. + * It must be readable and writable by anybody on the system. */ + if (rctx->lfd == -1) { + ret = create_pipe_fd(rctx->sock_name, &rctx->lfd, SCKT_RSP_UMASK); + if (ret != EOK) { + return ret; + } + } + + accept_ctx = talloc_zero(rctx, struct accept_fd_ctx); + if(!accept_ctx) goto failed; + accept_ctx->rctx = rctx; + accept_ctx->is_private = false; + accept_ctx->connection_setup = conn_setup; + + rctx->lfde = tevent_add_fd(rctx->ev, rctx, rctx->lfd, + TEVENT_FD_READ, accept_fd_handler, + accept_ctx); + if (!rctx->lfde) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to queue handler on pipe\n"); + goto failed; + } + } + + if (rctx->priv_sock_name != NULL ) { + /* create privileged pipe */ + if (rctx->priv_lfd == -1) { + ret = create_pipe_fd(rctx->priv_sock_name, &rctx->priv_lfd, + DFL_RSP_UMASK); + if (ret != EOK) { + goto failed; + } + } + + accept_ctx = talloc_zero(rctx, struct accept_fd_ctx); + if(!accept_ctx) goto failed; + accept_ctx->rctx = rctx; + accept_ctx->is_private = true; + accept_ctx->connection_setup = conn_setup; + + rctx->priv_lfde = tevent_add_fd(rctx->ev, rctx, rctx->priv_lfd, + TEVENT_FD_READ, accept_fd_handler, + accept_ctx); + if (!rctx->priv_lfde) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to queue handler on privileged pipe\n"); + goto failed; + } + } + + return EOK; + +failed: + if (rctx->lfd >= 0) close(rctx->lfd); + if (rctx->priv_lfd >= 0) close(rctx->priv_lfd); + return EIO; +} + +int activate_unix_sockets(struct resp_ctx *rctx, + connection_setup_t conn_setup) +{ + int ret; + +#ifdef HAVE_SYSTEMD + struct sockaddr_un sockaddr; + socklen_t sockaddr_len = sizeof(sockaddr); + + if (rctx->lfd == -1 && rctx->priv_lfd == -1) { + int numfds = (rctx->sock_name ? 1 : 0) + + (rctx->priv_sock_name ? 1 : 0); + /* but if systemd support is available, check if the sockets + * have been opened for us, via socket activation */ + ret = sd_listen_fds(1); + if (ret < 0) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Unexpected error probing for active sockets. " + "Will proceed with no sockets. [Error %d (%s)]\n", + -ret, sss_strerror(-ret)); + } else if (ret > numfds) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Too many activated sockets have been found, " + "expected %d, found %d\n", numfds, ret); + ret = E2BIG; + goto done; + } + + if (ret == numfds) { + rctx->lfd = SD_LISTEN_FDS_START; + ret = sd_is_socket_unix(rctx->lfd, SOCK_STREAM, 1, NULL, 0); + if (ret < 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Activated socket is not a UNIX listening socket\n"); + ret = EIO; + goto done; + } + + ret = getsockname(rctx->lfd, (struct sockaddr *) &sockaddr, &sockaddr_len); + if (ret == EOK) { + if (rctx->sock_name && + memcmp(rctx->sock_name, sockaddr.sun_path, strlen(rctx->sock_name)) != 0) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Warning: socket path defined in systemd unit (%s) and sssd.conf (%s) don't match\n", + sockaddr.sun_path, rctx->sock_name); + } + } + + ret = sss_fd_nonblocking(rctx->lfd); + if (ret != EOK) goto done; + if (numfds == 2) { + rctx->priv_lfd = SD_LISTEN_FDS_START + 1; + ret = sd_is_socket_unix(rctx->priv_lfd, SOCK_STREAM, 1, NULL, 0); + if (ret < 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Activated priv socket is not a UNIX listening socket\n"); + ret = EIO; + goto done; + } + + ret = sss_fd_nonblocking(rctx->priv_lfd); + if (ret != EOK) goto done; + } + } + } +#endif + + ret = set_unix_socket(rctx, conn_setup); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Fatal error initializing sockets\n"); + goto done; + } + +done: + return ret; +} + +void sss_client_fd_handler(void *ptr, + void (*recv_fn) (struct cli_ctx *cctx), + void (*send_fn) (struct cli_ctx *cctx), + uint16_t flags) +{ + errno_t ret; + uint64_t old_chain_id; + struct cli_ctx *cctx = talloc_get_type(ptr, struct cli_ctx); + + /* Always reset the responder idle timer on any activity */ + cctx->rctx->last_request_time = time(NULL); + + /* Always reset the client idle timer on any activity */ + ret = reset_client_idle_timer(cctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not create idle timer for the client. " + "This connection may not auto-terminate.\n"); + /* Non-fatal, continue */ + } + + /* Set the chain id */ + old_chain_id = sss_chain_id_set(cctx->client_id_num); + + if (flags & TEVENT_FD_READ) { + recv_fn(cctx); + return; + } + + if (flags & TEVENT_FD_WRITE) { + send_fn(cctx); + return; + } + /* Restore the original chain id */ + sss_chain_id_set(old_chain_id); +} + +int sss_connection_setup(struct cli_ctx *cctx) +{ + cctx->protocol_ctx = talloc_zero(cctx, struct cli_protocol); + if (!cctx->protocol_ctx) { + return ENOMEM; + } + + cctx->cfd_handler = client_fd_handler; + + return EOK; +} + +static int sss_responder_ctx_destructor(void *ptr) +{ + struct resp_ctx *rctx = talloc_get_type(ptr, struct resp_ctx); + + /* mark that we are shutting down the responder, so it is propagated + * into underlying contexts that are freed right before rctx */ + DEBUG(SSSDBG_TRACE_FUNC, "Responder is being shut down\n"); + rctx->shutting_down = true; + + return 0; +} + +static errno_t responder_init_ncache(TALLOC_CTX *mem_ctx, + struct confdb_ctx *cdb, + struct sss_nc_ctx **ncache) +{ + uint32_t neg_timeout; + uint32_t locals_timeout; + int tmp_value; + int ret; + + /* neg_timeout */ + ret = confdb_get_int(cdb, CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_ENTRY_NEG_TIMEOUT, + 15, &tmp_value); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Fatal failure of setup negative cache timeout [%s].\n", + CONFDB_NSS_ENTRY_NEG_TIMEOUT); + ret = ENOENT; + goto done; + } + + if (tmp_value < 0) { + ret = EINVAL; + goto done; + } + + neg_timeout = tmp_value; + + /* local_timeout */ + ret = confdb_get_int(cdb, CONFDB_NSS_CONF_ENTRY, + CONFDB_RESPONDER_LOCAL_NEG_TIMEOUT, + CONFDB_RESPONDER_LOCAL_NEG_TIMEOUT_DEFAULT, + &tmp_value); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Fatal failure of setup negative cache timeout [%s].\n", + CONFDB_RESPONDER_LOCAL_NEG_TIMEOUT); + ret = ENOENT; + goto done; + } + + if (tmp_value < 0) { + ret = EINVAL; + goto done; + } + + locals_timeout = tmp_value; + + /* negative cache init */ + ret = sss_ncache_init(mem_ctx, neg_timeout, locals_timeout, ncache); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Fatal failure of initializing negative cache.\n"); + goto done; + } + + ret = EOK; + +done: + return ret; +} + +static errno_t sss_get_etc_shells(TALLOC_CTX *mem_ctx, char ***_shells) +{ + int i = 0; + char *sh; + char **shells = NULL; + TALLOC_CTX *tmp_ctx; + errno_t ret; + int size; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + shells = talloc_array(tmp_ctx, char *, SHELL_REALLOC_INCREMENT); + if (!shells) { + ret = ENOMEM; + goto done; + } + size = SHELL_REALLOC_INCREMENT; + + setusershell(); + while ((sh = getusershell())) { + shells[i] = talloc_strdup(shells, sh); + if (!shells[i]) { + endusershell(); + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_FUNC, "Found shell %s in /etc/shells\n", shells[i]); + i++; + + if (i == size) { + size += SHELL_REALLOC_INCREMENT; + if (size > SHELL_REALLOC_MAX) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Reached maximum number of shells [%d]. " + "Users may be denied access. " + "Please check /etc/shells for sanity\n", + SHELL_REALLOC_MAX); + break; + } + shells = talloc_realloc(NULL, shells, char *, + size); + if (!shells) { + ret = ENOMEM; + goto done; + } + } + } + endusershell(); + + if (i + 1 < size) { + shells = talloc_realloc(NULL, shells, char *, i + 1); + if (!shells) { + ret = ENOMEM; + goto done; + } + } + shells[i] = NULL; + + *_shells = talloc_move(mem_ctx, &shells); + ret = EOK; +done: + talloc_zfree(tmp_ctx); + return ret; +} + +int sss_process_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct confdb_ctx *cdb, + struct sss_cmd_table sss_cmds[], + const char *sss_pipe_name, + int pipe_fd, + const char *sss_priv_pipe_name, + int priv_pipe_fd, + const char *confdb_service_path, + const char *conn_name, + const char *svc_name, + connection_setup_t conn_setup, + struct resp_ctx **responder_ctx) +{ + struct resp_ctx *rctx; + struct sss_domain_info *dom; + int ret; + char *tmp = NULL; + + rctx = talloc_zero(mem_ctx, struct resp_ctx); + if (!rctx) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error initializing resp_ctx\n"); + return ENOMEM; + } + rctx->ev = ev; + rctx->cdb = cdb; + rctx->sss_cmds = sss_cmds; + rctx->sock_name = sss_pipe_name; + rctx->priv_sock_name = sss_priv_pipe_name; + rctx->lfd = pipe_fd; + rctx->priv_lfd = priv_pipe_fd; + rctx->confdb_service_path = confdb_service_path; + rctx->shutting_down = false; + rctx->socket_activated = is_socket_activated(); + rctx->dbus_activated = is_dbus_activated(); + + talloc_set_destructor((TALLOC_CTX*)rctx, sss_responder_ctx_destructor); + + ret = confdb_get_int(rctx->cdb, rctx->confdb_service_path, + CONFDB_RESPONDER_CLI_IDLE_TIMEOUT, + CONFDB_RESPONDER_CLI_IDLE_DEFAULT_TIMEOUT, + &rctx->client_idle_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get the client idle timeout [%s] [%d]: %s\n", + CONFDB_RESPONDER_CLI_IDLE_TIMEOUT, ret, strerror(ret)); + goto fail; + } + + /* Ensure that the client timeout is at least ten seconds */ + if (rctx->client_idle_timeout < 10) { + rctx->client_idle_timeout = 10; + } + + if (rctx->socket_activated || rctx->dbus_activated) { + ret = responder_setup_idle_timeout_config(rctx); + if (ret != EOK) { + goto fail; + } + } + + ret = confdb_get_bool(rctx->cdb, rctx->confdb_service_path, + CONFDB_RESPONDER_CACHE_FIRST, + CONFDB_RESPONDER_CACHE_FIRST_DEFAILT, + &rctx->cache_first); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get \"cache_first_option\".\n" + "Querying the caches first before querying the " + "Data Providers will not be enforced [%d]: %s.\n", + ret, sss_strerror(ret)); + } + + ret = confdb_get_int(rctx->cdb, rctx->confdb_service_path, + CONFDB_RESPONDER_GET_DOMAINS_TIMEOUT, + GET_DOMAINS_DEFAULT_TIMEOUT, &rctx->domains_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get the default domain timeout [%s] [%d]: %s\n", + CONFDB_RESPONDER_GET_DOMAINS_TIMEOUT, ret, strerror(ret)); + goto fail; + } + + if (rctx->domains_timeout < 0) { + DEBUG(SSSDBG_CONF_SETTINGS, + "timeout [%s] can't be set to negative value, " + "setting default [%d] seconds.\n", + CONFDB_RESPONDER_GET_DOMAINS_TIMEOUT, + GET_DOMAINS_DEFAULT_TIMEOUT); + rctx->domains_timeout = GET_DOMAINS_DEFAULT_TIMEOUT; + } + + ret = confdb_get_domains(rctx->cdb, &rctx->domains); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error setting up domain map\n"); + goto fail; + } + + ret = confdb_get_string(rctx->cdb, rctx, CONFDB_MONITOR_CONF_ENTRY, + CONFDB_MONITOR_DEFAULT_DOMAIN, NULL, + &rctx->default_domain); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get the default domain [%d]: %s\n", + ret, strerror(ret)); + goto fail; + } + + ret = confdb_get_string(rctx->cdb, rctx, CONFDB_MONITOR_CONF_ENTRY, + CONFDB_MONITOR_OVERRIDE_SPACE, NULL, + &tmp); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get the space substitution character [%d]: %s\n", + ret, strerror(ret)); + goto fail; + } + + if (tmp != NULL) { + if (strlen(tmp) > 1) { + DEBUG(SSSDBG_MINOR_FAILURE, "Option %s is longer than 1 character " + "only the first character %c will be used\n", + CONFDB_MONITOR_OVERRIDE_SPACE, tmp[0]); + } + + rctx->override_space = tmp[0]; + } + + ret = confdb_get_string(rctx->cdb, rctx, + CONFDB_MONITOR_CONF_ENTRY, + CONFDB_MONITOR_DOMAIN_RESOLUTION_ORDER, NULL, + &tmp); + if (ret == EOK) { + rctx->domain_resolution_order = sss_replace_char(rctx, tmp, ',', ':'); + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot get the \"domain_resolution_order\" option.\n" + "The set up lookup_order won't be followed [%d]: %s.\n", + ret, sss_strerror(ret)); + } + + /* Read shell settings */ + ret = confdb_get_string(cdb, rctx, CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_OVERRIDE_SHELL, NULL, + &rctx->override_shell); + if (ret != EOK && ret != ENOENT) goto fail; + + ret = confdb_get_string_as_list(cdb, rctx, CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_ALLOWED_SHELL, + &rctx->allowed_shells); + if (ret != EOK && ret != ENOENT) goto fail; + + ret = confdb_get_string_as_list(cdb, rctx, CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_VETOED_SHELL, + &rctx->vetoed_shells); + if (ret != EOK && ret != ENOENT) goto fail; + + ret = sss_get_etc_shells(rctx, &rctx->etc_shells); + if (ret != EOK) goto fail; + + ret = confdb_get_string(cdb, rctx, CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_SHELL_FALLBACK, + CONFDB_DEFAULT_SHELL_FALLBACK, + &rctx->shell_fallback); + if (ret != EOK) goto fail; + + ret = confdb_get_string(cdb, rctx, CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_DEFAULT_SHELL, + NULL, + &rctx->default_shell); + if (ret != EOK) goto fail; + + /* Read session_recording section */ + ret = session_recording_conf_load(rctx, rctx->cdb, &rctx->sr_conf); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed loading session recording configuration: %s\n", + strerror(ret)); + goto fail; + } + + for (dom = rctx->domains; dom; dom = get_next_domain(dom, 0)) { + ret = sss_names_init(rctx->cdb, rctx->cdb, dom->name, &dom->names); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "fatal error initializing regex data for domain: %s\n", + dom->name); + goto fail; + } + + ret = sss_dp_init(rctx, conn_name, svc_name, dom); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "fatal error setting up backend connector\n"); + goto fail; + } + } + + ret = sysdb_init(rctx, rctx->domains); + if (ret != EOK) { + SYSDB_VERSION_ERROR_DAEMON(ret); + DEBUG(SSSDBG_FATAL_FAILURE, + "fatal error initializing sysdb connection\n"); + goto fail; + } + + /* after all initializations we are ready to listen on our socket */ + ret = activate_unix_sockets(rctx, conn_setup); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error initializing socket\n"); + goto fail; + } + + ret = responder_init_ncache(rctx, rctx->cdb, &rctx->ncache); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "fatal error initializing negcache\n"); + goto fail; + } + + ret = sss_ad_default_names_ctx(rctx, &rctx->global_names); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_ad_default_names_ctx failed.\n"); + goto fail; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Responder initialization complete (%s)\n", + rctx->socket_activated ? "socket-activated" : + rctx->dbus_activated ? "dbus-activated" : + "explicitly configured"); + + *responder_ctx = rctx; + return EOK; + +fail: + talloc_free(rctx); + return ret; +} + +int sss_dp_get_domain_conn(struct resp_ctx *rctx, const char *domain, + struct be_conn **_conn) +{ + struct be_conn *iter; + + if (!rctx->be_conns) return ENOENT; + + for (iter = rctx->be_conns; iter; iter = iter->next) { + if (strcasecmp(domain, iter->domain->name) == 0) break; + } + + if (!iter) return ENOENT; + + *_conn = iter; + + return EOK; +} + +struct sss_domain_info * +responder_get_domain(struct resp_ctx *rctx, const char *name) +{ + struct sss_domain_info *dom; + struct sss_domain_info *ret_dom = NULL; + + for (dom = rctx->domains; dom; + dom = get_next_domain(dom, SSS_GND_DESCEND)) { + if (sss_domain_get_state(dom) == DOM_DISABLED) { + continue; + } + + if (strcasecmp(dom->name, name) == 0 || + (dom->flat_name != NULL && + strcasecmp(dom->flat_name, name) == 0)) { + ret_dom = dom; + break; + } + } + + if (!ret_dom) { + DEBUG(SSSDBG_OP_FAILURE, "Unknown domain [%s]\n", name); + } + + return ret_dom; +} + +errno_t responder_get_domain_by_id(struct resp_ctx *rctx, const char *id, + struct sss_domain_info **_ret_dom) +{ + struct sss_domain_info *dom; + struct sss_domain_info *ret_dom = NULL; + size_t id_len; + size_t dom_id_len; + int ret; + + if (id == NULL || _ret_dom == NULL) { + return EINVAL; + } + + id_len = strlen(id); + + for (dom = rctx->domains; dom; + dom = get_next_domain(dom, SSS_GND_DESCEND)) { + if (sss_domain_get_state(dom) == DOM_DISABLED || + dom->domain_id == NULL) { + continue; + } + + dom_id_len = strlen(dom->domain_id); + if ((id_len >= dom_id_len) && + strncasecmp(dom->domain_id, id, dom_id_len) == 0) { + if (IS_SUBDOMAIN(dom) && + ((time(NULL) - dom->parent->subdomains_last_checked.tv_sec) > + rctx->domains_timeout)) { + DEBUG(SSSDBG_TRACE_FUNC, "Domain entry with id [%s] " \ + "is expired.\n", id); + ret = EAGAIN; + goto done; + } + ret_dom = dom; + break; + } + } + + if (ret_dom == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Unknown domain id [%s], checking for " + "possible subdomains!\n", id); + ret = ENOENT; + } else { + *_ret_dom = ret_dom; + ret = EOK; + } + +done: + return ret; +} + +errno_t +responder_logrotate(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct resp_ctx *rctx) +{ + return server_common_rotate_logs(rctx->cdb, rctx->confdb_service_path); + + return EOK; +} + +void responder_set_fd_limit(rlim_t fd_limit) +{ + struct rlimit current_limit, new_limit; + int limret; + + /* First, let's see if we have permission to just set + * the value as-is. + */ + new_limit.rlim_cur = fd_limit; + new_limit.rlim_max = fd_limit; + limret = setrlimit(RLIMIT_NOFILE, &new_limit); + if (limret == 0) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Maximum file descriptors set to [%"SPRIrlim"]\n", + new_limit.rlim_cur); + return; + } + + /* We couldn't set the soft and hard limits to this + * value. Let's see how high we CAN set it. + */ + + /* Determine the maximum hard limit */ + limret = getrlimit(RLIMIT_NOFILE, ¤t_limit); + if (limret == 0) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Current fd limit: [%"SPRIrlim"]\n", + current_limit.rlim_cur); + /* Choose the lesser of the requested and the hard limit */ + if (current_limit.rlim_max < fd_limit) { + new_limit.rlim_cur = current_limit.rlim_max; + } else { + new_limit.rlim_cur = fd_limit; + } + new_limit.rlim_max = current_limit.rlim_max; + + limret = setrlimit(RLIMIT_NOFILE, &new_limit); + if (limret == 0) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Maximum file descriptors set to [%"SPRIrlim"]\n", + new_limit.rlim_cur); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not set new fd limits. Proceeding with " + "[%"SPRIrlim"]\n", current_limit.rlim_cur); + } + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not determine fd limits. " + "Proceeding with system values\n"); + } +} + +errno_t responder_setup_idle_timeout_config(struct resp_ctx *rctx) +{ + errno_t ret; + + ret = confdb_get_int(rctx->cdb, rctx->confdb_service_path, + CONFDB_RESPONDER_IDLE_TIMEOUT, + CONFDB_RESPONDER_IDLE_DEFAULT_TIMEOUT, + &rctx->idle_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get the responder idle timeout [%s] [%d]: %s\n", + CONFDB_RESPONDER_IDLE_TIMEOUT, ret, sss_strerror(ret)); + goto fail; + } + + /* Idle timeout set to 0 means that no timeout will be set up to + * the responder */ + if (rctx->idle_timeout == 0) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Responder idle timeout won't be set up as the " + "responder_idle_timeout is set to 0\n"); + } else { + /* Ensure that the responder timeout is at least sixty seconds */ + if (rctx->idle_timeout < 60) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "responder_idle_timeout is set to a value lower than " + "the minimum allowed (60s). " + "The minimum allowed value will be used.\n"); + + rctx->idle_timeout = 60; + } + + ret = setup_responder_idle_timer(rctx); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "An error occurred when setting up the responder's idle " + "timeout [%s] for the responder [%p]: %s [%d].\n" + "The responder won't be automatically shutdown after %d " + "seconds inactive.\n", + CONFDB_RESPONDER_IDLE_TIMEOUT, + rctx, sss_strerror(ret), ret, + rctx->idle_timeout); + } + } + + ret = EOK; + +fail: + return ret; + +} + +/* ====== Helper functions for the domain resolution order ======= */ +static errno_t +sss_resp_new_cr_domains_from_ipa_id_view(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domains, + struct sysdb_ctx *sysdb, + struct cache_req_domain **_cr_domains) +{ + TALLOC_CTX *tmp_ctx; + struct cache_req_domain *cr_domains = NULL; + const char *domain_resolution_order = NULL; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sysdb_get_view_domain_resolution_order(tmp_ctx, sysdb, + &domain_resolution_order); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "sysdb_get_view_cache_req_domain() failed [%d]: [%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + if (ret == ENOENT) { + goto done; + } + + ret = cache_req_domain_new_list_from_domain_resolution_order( + mem_ctx, domains, domain_resolution_order, &cr_domains); + if (ret != EOK) { + DEBUG(SSSDBG_DEFAULT, + "cache_req_domain_new_list_from_domain_resolution_order() " + "failed [%d]: [%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + *_cr_domains = cr_domains; + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +sss_resp_new_cr_domains_from_ipa_config(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domains, + struct sysdb_ctx *sysdb, + const char *domain, + struct cache_req_domain **_cr_domains) +{ + TALLOC_CTX *tmp_ctx; + const char *domain_resolution_order = NULL; + errno_t ret; + + *_cr_domains = NULL; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sysdb_domain_get_domain_resolution_order(tmp_ctx, sysdb, domain, + &domain_resolution_order); + + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "sysdb_domain_get_cache_req_domain() failed [%d]: [%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + if (ret == ENOENT) { + goto done; + } + + ret = cache_req_domain_new_list_from_domain_resolution_order( + mem_ctx, domains, domain_resolution_order, _cr_domains); + if (ret != EOK) { + DEBUG(SSSDBG_DEFAULT, + "cache_req_domain_new_list_from_domain_resolution_order() " + "failed [%d]: [%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t sss_resp_populate_cr_domains(struct resp_ctx *rctx) +{ + struct cache_req_domain *cr_domains = NULL; + struct sss_domain_info *dom; + errno_t ret; + + if (rctx->domain_resolution_order != NULL) { + ret = cache_req_domain_new_list_from_domain_resolution_order( + rctx, rctx->domains, + rctx->domain_resolution_order, &cr_domains); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_FUNC, + "Using domain_resolution_order from sssd.conf\n"); + goto done; + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to use domain_resolution_order set in the config file.\n" + "Trying to fallback to use ipaDomainOrderResolution setup by " + "IPA.\n"); + } + } + + for (dom = rctx->domains; dom != NULL; dom = dom->next) { + if (dom->provider != NULL && strcmp(dom->provider, "ipa") == 0) { + break; + } + } + + if (dom == NULL) { + ret = cache_req_domain_new_list_from_domain_resolution_order( + rctx, rctx->domains, NULL, &cr_domains); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to flatten the list of domains.\n"); + } + goto done; + } + + if (dom->has_views) { + ret = sss_resp_new_cr_domains_from_ipa_id_view(rctx, rctx->domains, + dom->sysdb, + &cr_domains); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_FUNC, + "Using domain_resolution_order from IPA ID View\n"); + goto done; + } + + if (ret != ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to use ipaDomainResolutionOrder set for the " + "view \"%s\".\n" + "Trying to fallback to use ipaDomainOrderResolution " + "set in ipaConfig for the domain: %s.\n", + dom->view_name, dom->name); + } + } + + ret = sss_resp_new_cr_domains_from_ipa_config(rctx, rctx->domains, + dom->sysdb, dom->name, + &cr_domains); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_FUNC, + "Using domain_resolution_order from IPA Config\n"); + goto done; + } + + if (ret != ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to use ipaDomainResolutionOrder set in ipaConfig " + "for the domain: \"%s\".\n" + "No ipaDomainResolutionOrder will be followed.\n", + dom->name); + } + + ret = cache_req_domain_new_list_from_domain_resolution_order( + rctx, rctx->domains, NULL, &cr_domains); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to flatten the list of domains.\n"); + goto done; + } + + ret = EOK; + +done: + cache_req_domain_list_zfree(&rctx->cr_domains); + rctx->cr_domains = cr_domains; + + return ret; +} + +/** + * Helper functions to format output names + */ +int sized_output_name(TALLOC_CTX *mem_ctx, + struct resp_ctx *rctx, + const char *orig_name, + struct sss_domain_info *name_dom, + struct sized_string **_name) +{ + TALLOC_CTX *tmp_ctx = NULL; + errno_t ret; + char *name_str; + struct sized_string *name; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + name = talloc_zero(tmp_ctx, struct sized_string); + if (name == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sss_output_fqname(name, name_dom, orig_name, + rctx->override_space, &name_str); + if (ret != EOK) { + goto done; + } + + to_sized_string(name, name_str); + *_name = talloc_steal(mem_ctx, name); + ret = EOK; +done: + talloc_zfree(tmp_ctx); + return ret; +} + +int sized_domain_name(TALLOC_CTX *mem_ctx, + struct resp_ctx *rctx, + const char *member_name, + struct sized_string **_name) +{ + TALLOC_CTX *tmp_ctx = NULL; + errno_t ret; + char *domname; + struct sss_domain_info *member_dom; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sss_parse_internal_fqname(tmp_ctx, member_name, NULL, &domname); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_parse_internal_fqname failed\n"); + goto done; + } + + if (domname == NULL) { + ret = ERR_WRONG_NAME_FORMAT; + goto done; + } + + member_dom = find_domain_by_name(get_domains_head(rctx->domains), + domname, true); + if (member_dom == NULL) { + ret = ERR_DOMAIN_NOT_FOUND; + goto done; + } + + ret = sized_output_name(mem_ctx, rctx, member_name, + member_dom, _name); +done: + talloc_free(tmp_ctx); + return ret; +} diff --git a/src/responder/common/responder_dp.c b/src/responder/common/responder_dp.c new file mode 100644 index 0000000..227a229 --- /dev/null +++ b/src/responder/common/responder_dp.c @@ -0,0 +1,460 @@ +/* + Authors: + Simo Sorce <ssorce@redhat.com> + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2009 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 <sys/time.h> +#include <time.h> +#include "util/util.h" +#include "util/sss_chain_id.h" +#include "responder/common/responder_packet.h" +#include "responder/common/responder.h" +#include "providers/data_provider.h" + +#ifdef BUILD_FILES_PROVIDER +static errno_t +sss_dp_account_files_params(struct sss_domain_info *dom, + enum sss_dp_acct_type type_in, + const char *opt_name_in, + enum sss_dp_acct_type *_type_out, + const char **_opt_name_out) +{ + if (type_in != SSS_DP_CERT) { + if (sss_domain_get_state(dom) != DOM_INCONSISTENT) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "The entries in the files domain are up-to-date\n"); + return EOK; + } + + if (sss_domain_fallback_to_nss(dom)) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Domain files is not consistent, falling back to nss.\n"); + return ENOENT; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Domain files is not consistent, issuing update\n"); + } + + switch(type_in) { + case SSS_DP_USER: + case SSS_DP_GROUP: + *_type_out = type_in; + *_opt_name_out = NULL; + return EAGAIN; + case SSS_DP_INITGROUPS: + /* There is no initgroups enumeration so let's use a dummy + * name to let the DP chain the requests + */ + *_type_out = type_in; + *_opt_name_out = DP_REQ_OPT_FILES_INITGR; + return EAGAIN; + case SSS_DP_CERT: + /* Let the backend handle certificate mapping for local users */ + *_type_out = type_in; + *_opt_name_out = opt_name_in; + return EAGAIN; + /* These are not handled by the files provider, just fall back */ + case SSS_DP_SUBID_RANGES: + case SSS_DP_NETGR: + case SSS_DP_SERVICES: + case SSS_DP_SECID: + case SSS_DP_USER_AND_GROUP: + case SSS_DP_WILDCARD_USER: + case SSS_DP_WILDCARD_GROUP: + return EOK; + } + + DEBUG(SSSDBG_CRIT_FAILURE, "Unhandled type %d\n", type_in); + return EINVAL; +} +#endif + +static errno_t +sss_dp_get_account_filter(TALLOC_CTX *mem_ctx, + enum sss_dp_acct_type type, + bool fast_reply, + const char *opt_name, + uint32_t opt_id, + uint32_t *_dp_flags, + uint32_t *_entry_type, + char **_filter) +{ + uint32_t entry_type = 0; + uint32_t dp_flags; + char *filter; + + switch (type) { + case SSS_DP_USER: + case SSS_DP_WILDCARD_USER: + entry_type = BE_REQ_USER; + break; + case SSS_DP_GROUP: + case SSS_DP_WILDCARD_GROUP: + entry_type = BE_REQ_GROUP; + break; + case SSS_DP_INITGROUPS: + entry_type = BE_REQ_INITGROUPS; + break; + case SSS_DP_SUBID_RANGES: + entry_type = BE_REQ_SUBID_RANGES; + break; + case SSS_DP_NETGR: + entry_type = BE_REQ_NETGROUP; + break; + case SSS_DP_SERVICES: + entry_type = BE_REQ_SERVICES; + break; + case SSS_DP_SECID: + entry_type = BE_REQ_BY_SECID; + break; + case SSS_DP_USER_AND_GROUP: + entry_type = BE_REQ_USER_AND_GROUP; + break; + case SSS_DP_CERT: + entry_type = BE_REQ_BY_CERT; + break; + } + + dp_flags = fast_reply ? DP_FAST_REPLY : 0; + + if (opt_name != NULL) { + switch(type) { + case SSS_DP_SECID: + filter = talloc_asprintf(mem_ctx, "%s=%s", DP_SEC_ID, + opt_name); + break; + case SSS_DP_CERT: + filter = talloc_asprintf(mem_ctx, "%s=%s", DP_CERT, + opt_name); + break; + case SSS_DP_WILDCARD_USER: + case SSS_DP_WILDCARD_GROUP: + filter = talloc_asprintf(mem_ctx, "%s=%s", DP_WILDCARD, + opt_name); + break; + default: + filter = talloc_asprintf(mem_ctx, "name=%s", opt_name); + break; + } + } else if (opt_id != 0) { + filter = talloc_asprintf(mem_ctx, "idnumber=%u", opt_id); + } else { + filter = talloc_strdup(mem_ctx, ENUM_INDICATOR); + } + + if (filter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory?!\n"); + return ENOMEM; + } + + *_dp_flags = dp_flags; + *_entry_type = entry_type; + *_filter = filter; + + return EOK; +} + +struct sss_dp_get_account_state { + uint16_t dp_error; + uint32_t error; + const char *error_message; +}; + +static void sss_dp_get_account_done(struct tevent_req *subreq); + +struct tevent_req * +sss_dp_get_account_send(TALLOC_CTX *mem_ctx, + struct resp_ctx *rctx, + struct sss_domain_info *dom, + bool fast_reply, + enum sss_dp_acct_type type, + const char *opt_name, + uint32_t opt_id, + const char *extra) +{ + struct sss_dp_get_account_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + struct be_conn *be_conn; + uint32_t entry_type; + uint32_t dp_flags; + char *filter; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct sss_dp_get_account_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + /* either, or, not both */ + if (opt_name != NULL && opt_id != 0) { + ret = EINVAL; + goto done; + } + + if (dom == NULL) { + ret = EINVAL; + goto done; + } + +#ifdef BUILD_FILES_PROVIDER + if (is_files_provider(dom)) { + /* This is a special case. If the files provider is just being updated, + * we issue an enumeration request. We always use the same request type + * (user enumeration) to make sure concurrent requests are just chained + * in the Data Provider */ + ret = sss_dp_account_files_params(dom, type, opt_name, + &type, &opt_name); + if (ret == EOK) { + state->dp_error = DP_ERR_OK; + state->error = EOK; + state->error_message = talloc_strdup(state, "Success"); + if (state->error_message == NULL) { + ret = ENOMEM; + goto done; + } + goto done; + } else if (ret != EAGAIN) { + DEBUG((ret == ENOENT) ? SSSDBG_MINOR_FAILURE : SSSDBG_OP_FAILURE, + "Failed to set files provider update [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + /* EAGAIN, fall through to issuing the request */ + } +#endif + + ret = sss_dp_get_domain_conn(rctx, dom->conn_name, &be_conn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "BUG: The Data Provider connection for %s is not available!\n", + dom->name); + ret = EIO; + goto done; + } + + /* Build filter. */ + ret = sss_dp_get_account_filter(state, type, fast_reply, opt_name, opt_id, + &dp_flags, &entry_type, &filter); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Creating request for [%s][%#x][%s][%s:%s]\n", + dom->name, entry_type, be_req2str(entry_type), + filter, extra == NULL ? "-" : extra); + + subreq = sbus_call_dp_dp_getAccountInfo_send(state, be_conn->conn, + be_conn->bus_name, SSS_BUS_PATH, dp_flags, + entry_type, filter, dom->name, extra, + sss_chain_id_get()); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sss_dp_get_account_done, req); + + ret = EAGAIN; + +done: + if (ret == EOK) { + tevent_req_done(req); + tevent_req_post(req, rctx->ev); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, rctx->ev); + } + + return req; +} + +static void sss_dp_get_account_done(struct tevent_req *subreq) +{ + struct sss_dp_get_account_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sss_dp_get_account_state); + + ret = sbus_call_dp_dp_getAccountInfo_recv(state, subreq, &state->dp_error, + &state->error, + &state->error_message); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +errno_t +sss_dp_get_account_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + uint16_t *_dp_error, + uint32_t *_error, + const char **_error_message) +{ + struct sss_dp_get_account_state *state; + state = tevent_req_data(req, struct sss_dp_get_account_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_dp_error = state->dp_error; + *_error = state->error; + *_error_message = talloc_steal(mem_ctx, state->error_message); + + return EOK; +} + +struct sss_dp_resolver_get_state { + uint16_t dp_error; + uint32_t error; + const char *error_message; +}; + +static void sss_dp_resolver_get_done(struct tevent_req *subreq); + +struct tevent_req * +sss_dp_resolver_get_send(TALLOC_CTX *mem_ctx, + struct resp_ctx *rctx, + struct sss_domain_info *dom, + bool fast_reply, + uint32_t entry_type, + uint32_t filter_type, + const char *filter_value) +{ + struct sss_dp_resolver_get_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + struct be_conn *be_conn; + uint32_t dp_flags; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct sss_dp_resolver_get_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + /* Validate filter_type */ + switch (filter_type) { + case BE_FILTER_NAME: + case BE_FILTER_ADDR: + case BE_FILTER_ENUM: + break; + default: + ret = EINVAL; + goto done; + } + + if (dom == NULL) { + ret = EINVAL; + goto done; + } + + ret = sss_dp_get_domain_conn(rctx, dom->conn_name, &be_conn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "BUG: The Data Provider connection for %s is not available!\n", + dom->name); + ret = EIO; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Creating request for [%s][%#x][%s][%#x:%s]\n", + dom->name, entry_type, be_req2str(entry_type), + filter_type, filter_value ? filter_value : "-"); + + dp_flags = fast_reply ? DP_FAST_REPLY : 0; + subreq = sbus_call_dp_dp_resolverHandler_send(state, be_conn->conn, + be_conn->bus_name, + SSS_BUS_PATH, + dp_flags, entry_type, + filter_type, filter_value, + sss_chain_id_get()); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sss_dp_resolver_get_done, req); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, rctx->ev); + } + + return req; +} + +static void sss_dp_resolver_get_done(struct tevent_req *subreq) +{ + struct sss_dp_resolver_get_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sss_dp_resolver_get_state); + + ret = sbus_call_dp_dp_resolverHandler_recv(state, subreq, + &state->dp_error, + &state->error, + &state->error_message); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +errno_t +sss_dp_resolver_get_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + uint16_t *_dp_error, + uint32_t *_error, + const char **_error_message) +{ + struct sss_dp_resolver_get_state *state; + state = tevent_req_data(req, struct sss_dp_resolver_get_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_dp_error = state->dp_error; + *_error = state->error; + *_error_message = talloc_steal(mem_ctx, state->error_message); + + return EOK; +} diff --git a/src/responder/common/responder_get_domains.c b/src/responder/common/responder_get_domains.c new file mode 100644 index 0000000..1a388e7 --- /dev/null +++ b/src/responder/common/responder_get_domains.c @@ -0,0 +1,841 @@ +/* + Authors: + Jan Zeleny <jzeleny@redhat.com> + + Copyright (C) 2011 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 "util/util.h" +#include "util/sss_chain_id.h" +#include "responder/common/responder.h" +#include "providers/data_provider.h" +#include "db/sysdb.h" +#include "sss_iface/sss_iface_async.h" + +/* ========== Get subdomains for a domain ================= */ +struct get_subdomains_state { + uint16_t dp_error; + uint32_t error; + const char *error_message; +}; + +static void get_subdomains_done(struct tevent_req *subreq); + +struct tevent_req * +get_subdomains_send(TALLOC_CTX *mem_ctx, + struct resp_ctx *rctx, + struct sss_domain_info *dom, + const char *hint) +{ + struct get_subdomains_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + struct be_conn *be_conn; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct get_subdomains_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + if (is_files_provider(dom)) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Domain %s does not check DP\n", + dom->name); + state->dp_error = DP_ERR_OK; + state->error = EOK; + state->error_message = talloc_strdup(state, "Success"); + if (state->error_message == NULL) { + ret = ENOMEM; + goto done; + } + ret = EOK; + goto done; + } + + ret = sss_dp_get_domain_conn(rctx, dom->conn_name, &be_conn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "BUG: The Data Provider connection for %s is not available!\n", + dom->name); + ret = EIO; + goto done; + } + + subreq = sbus_call_dp_dp_getDomains_send(state, be_conn->conn, + be_conn->bus_name, + SSS_BUS_PATH, hint); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, get_subdomains_done, req); + + ret = EAGAIN; + +done: +#ifdef BUILD_FILES_PROVIDER + if (ret == EOK) { + tevent_req_done(req); + tevent_req_post(req, rctx->ev); + } else +#endif + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, rctx->ev); + } + + return req; +} + +static void get_subdomains_done(struct tevent_req *subreq) +{ + struct get_subdomains_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct get_subdomains_state); + + ret = sbus_call_dp_dp_getDomains_recv(state, subreq, &state->dp_error, + &state->error, &state->error_message); + talloc_zfree(subreq); + if (ret != EOK) { + state->dp_error = DP_ERR_FATAL; + state->error = ret; + } + + tevent_req_done(req); + return; +} + +static errno_t +get_subdomains_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + uint16_t *_dp_error, + uint32_t *_error, + const char **_error_message) +{ + struct get_subdomains_state *state; + state = tevent_req_data(req, struct get_subdomains_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_dp_error = state->dp_error; + *_error = state->error; + *_error_message = talloc_steal(mem_ctx, state->error_message); + + return EOK; +} + +/* ====== Iterate over all domains, searching for their subdomains ======= */ +static errno_t process_subdomains(struct sss_domain_info *dom, + struct confdb_ctx *confdb); +static void set_time_of_last_request(struct resp_ctx *rctx); +static errno_t check_last_request(struct resp_ctx *rctx, const char *hint); + +struct sss_dp_get_domains_state { + struct resp_ctx *rctx; + struct sss_domain_info *dom; + const char *hint; +}; + +static void +sss_dp_get_domains_process(struct tevent_req *subreq); + +struct tevent_req *sss_dp_get_domains_send(TALLOC_CTX *mem_ctx, + struct resp_ctx *rctx, + bool force, + const char *hint) +{ + errno_t ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct sss_dp_get_domains_state *state; + bool refresh_timeout = false; + + req = tevent_req_create(mem_ctx, &state, struct sss_dp_get_domains_state); + if (req == NULL) { + return NULL; + } + + if (rctx->domains == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "No domains configured.\n"); + ret = EINVAL; + goto immediately; + } + + if (!force) { + ret = check_last_request(rctx, hint); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_FUNC, + "Last call was too recent, nothing to do!\n"); + goto immediately; + } else if (ret != EAGAIN) { + DEBUG(SSSDBG_TRACE_FUNC, "check_domain_request failed with [%d][%s]\n", + ret, strerror(ret)); + goto immediately; + } + } + refresh_timeout = true; + + state->rctx = rctx; + if (hint != NULL) { + state->hint = hint; + } else { + state->hint = talloc_strdup(state, ""); + if (state->hint == NULL) { + ret = ENOMEM; + goto immediately; + } + } + + state->dom = rctx->domains; + while(is_files_provider(state->dom)) { + state->dom = get_next_domain(state->dom, 0); + } + + if (state->dom == NULL) { + /* All domains were local */ + ret = sss_resp_populate_cr_domains(state->rctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_resp_populate_cr_domains() failed [%d]: [%s]\n", + ret, sss_strerror(ret)); + goto immediately; + } + ret = EOK; + goto immediately; + } + + subreq = get_subdomains_send(req, rctx, state->dom, state->hint); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + tevent_req_set_callback(subreq, sss_dp_get_domains_process, req); + + return req; + +immediately: + if (ret == EOK) { + if (refresh_timeout) { + set_time_of_last_request(rctx); + } + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, rctx->ev); + + return req; +} + +static void sss_resp_update_certmaps(struct resp_ctx *rctx) +{ + int ret; + struct certmap_info **certmaps; + bool user_name_hint; + struct sss_domain_info *dom; + + for (dom = rctx->domains; dom != NULL; dom = dom->next) { + ret = sysdb_get_certmap(dom, dom->sysdb, &certmaps, &user_name_hint); + if (ret == EOK) { + dom->user_name_hint = user_name_hint; + talloc_free(dom->certmaps); + dom->certmaps = certmaps; + } else { + DEBUG(SSSDBG_OP_FAILURE, + "sysdb_get_certmap failed for domain [%s].\n", dom->name); + } + } +} + +static void +sss_dp_get_domains_process(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sss_dp_get_domains_state *state = tevent_req_data(req, + struct sss_dp_get_domains_state); + uint16_t dp_err; + uint32_t dp_ret; + const char *err_msg; + + ret = get_subdomains_recv(subreq, subreq, &dp_err, &dp_ret, &err_msg); + talloc_zfree(subreq); + if (ret != EOK) { + goto fail; + } + + ret = process_subdomains(state->dom, state->rctx->cdb); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "process_subdomains failed, " + "trying next domain.\n"); + goto fail; + } + + /* Advance to the next domain */ + state->dom = get_next_domain(state->dom, 0); + + /* Skip "files provider" */ + while(is_files_provider(state->dom)) { + state->dom = get_next_domain(state->dom, 0); + } + + if (state->dom == NULL) { + /* No more domains to check, refreshing the active configuration */ + set_time_of_last_request(state->rctx); + ret = sss_resp_populate_cr_domains(state->rctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_resp_populate_cr_domains() failed [%d]: [%s]\n", + ret, sss_strerror(ret)); + goto fail; + } + + sss_resp_update_certmaps(state->rctx); + + ret = sss_ncache_reset_repopulate_permanent(state->rctx, + state->rctx->ncache); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_ncache_reset_repopulate_permanent failed, ignored.\n"); + } + + tevent_req_done(req); + return; + } + + subreq = get_subdomains_send(req, state->rctx, state->dom, state->hint); + if (subreq == NULL) { + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, sss_dp_get_domains_process, req); + return; + +fail: + tevent_req_error(req, ret); + return; +} + +static errno_t +process_subdomains(struct sss_domain_info *domain, struct confdb_ctx *confdb) +{ + int ret; + + if (domain->realm == NULL || + domain->flat_name == NULL || + domain->domain_id == NULL) { + ret = sysdb_master_domain_update(domain); + if (ret != EOK) { + DEBUG(SSSDBG_FUNC_DATA, "sysdb_master_domain_get_info " \ + "failed.\n"); + goto done; + } + } + + /* Retrieve all subdomains of this domain from sysdb + * and create their struct sss_domain_info representations + */ + ret = sysdb_update_subdomains(domain, confdb); + if (ret != EOK) { + DEBUG(SSSDBG_FUNC_DATA, "sysdb_update_subdomains failed.\n"); + goto done; + } + + errno = 0; + ret = gettimeofday(&domain->subdomains_last_checked, NULL); + if (ret == -1) { + ret = errno; + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to update sub-domains " + "of domain [%s].\n", domain->name); + } + + return ret; +} + +errno_t sss_dp_get_domains_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +static void set_time_of_last_request(struct resp_ctx *rctx) +{ + int ret; + + errno = 0; + ret = gettimeofday(&rctx->get_domains_last_call, NULL); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_TRACE_FUNC, "gettimeofday failed [%d][%s].\n", + ret, strerror(ret)); + } +} + +static errno_t check_last_request(struct resp_ctx *rctx, const char *hint) +{ + struct sss_domain_info *dom; + time_t now = time(NULL); + time_t diff; + + diff = now - rctx->get_domains_last_call.tv_sec; + if (diff >= rctx->domains_timeout) { + /* Timeout, expired, fetch domains again */ + return EAGAIN; + } + + if (hint != NULL) { + for (dom = rctx->domains; dom; + dom = get_next_domain(dom, SSS_GND_DESCEND)) { + if (!IS_SUBDOMAIN(dom)) { + diff = now - dom->subdomains_last_checked.tv_sec; + /* not a subdomain */ + continue; + } + if (strcasecmp(dom->name, hint) == 0) { + if (diff >= rctx->domains_timeout) { + /* Timeout, expired, fetch domains again */ + return EAGAIN; + } + } + } + } + + return EOK; +} + +struct get_domains_state { + struct resp_ctx *rctx; + struct sss_nc_ctx *optional_ncache; + get_domains_callback_fn_t *callback; + void *callback_pvt; +}; + +static void get_domains_at_startup_done(struct tevent_req *req) +{ + int ret; + struct get_domains_state *state; + + state = tevent_req_callback_data(req, struct get_domains_state); + + ret = sss_dp_get_domains_recv(req); + talloc_free(req); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "sss_dp_get_domains request failed.\n"); + } + + if (state->optional_ncache != NULL) { + ret = sss_ncache_reset_repopulate_permanent(state->rctx, + state->optional_ncache); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "sss_ncache_reset_repopulate_permanent failed.\n"); + } + } + + if (is_files_provider(state->rctx->domains)) { + ret = sysdb_master_domain_update(state->rctx->domains); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_master_domain_update failed, " + "ignored.\n"); + } + } + + if (state->callback != NULL) { + state->callback(state->callback_pvt); + } + + talloc_free(state); + return; +} + +static void get_domains_at_startup(struct tevent_context *ev, + struct tevent_immediate *imm, + void *pvt) +{ + struct tevent_req *req; + struct get_domains_state *state; + + state = talloc_get_type(pvt, struct get_domains_state); + + req = sss_dp_get_domains_send(state, state->rctx, true, NULL); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sss_dp_get_domains_send failed.\n"); + talloc_free(state); + return; + } + + tevent_req_set_callback(req, get_domains_at_startup_done, state); + return; +} + +errno_t schedule_get_domains_task(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_nc_ctx *optional_ncache, + get_domains_callback_fn_t *callback, + void *callback_pvt) +{ + struct tevent_immediate *imm; + struct get_domains_state *state; + + state = talloc(mem_ctx, struct get_domains_state); + if (state == NULL) { + return ENOMEM; + } + state->rctx = rctx; + state->optional_ncache = optional_ncache; + state->callback = callback; + state->callback_pvt = callback_pvt; + + imm = tevent_create_immediate(mem_ctx); + if (imm == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "tevent_create_immediate failed.\n"); + talloc_free(state); + return ENOMEM; + } + + tevent_schedule_immediate(imm, ev, get_domains_at_startup, state); + + return EOK; +} + +struct sss_parse_inp_state { + struct resp_ctx *rctx; + const char *default_domain; + const char *rawinp; + + char *name; + char *domname; + errno_t error; +}; + +static void sss_parse_inp_done(struct tevent_req *subreq); + +struct tevent_req * +sss_parse_inp_send(TALLOC_CTX *mem_ctx, + struct resp_ctx *rctx, + const char *default_domain, + const char *rawinp) +{ + errno_t ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct sss_parse_inp_state *state; + + req = tevent_req_create(mem_ctx, &state, struct sss_parse_inp_state); + if (req == NULL) { + return NULL; + } + + if (rawinp == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Empty input!\n"); + ret = EINVAL; + goto done; + } + + state->rctx = rctx; + + state->rawinp = talloc_strdup(state, rawinp); + if (state->rawinp == NULL) { + ret = ENOMEM; + goto done; + } + + + state->default_domain = talloc_strdup(state, default_domain); + if (default_domain != NULL && state->default_domain == NULL) { + ret = ENOMEM; + goto done; + } + + /* If the subdomains haven't been checked yet, we need to always + * attach to the post-startup subdomain request and only then parse + * the input. Otherwise, we might not be able to parse input with a + * flat domain name specifier */ + if (rctx->get_domains_last_call.tv_sec > 0) { + ret = sss_parse_name_for_domains(state, rctx->domains, + default_domain, rawinp, + &state->domname, &state->name); + if (ret == EOK) { + /* Was able to use cached domains */ + goto done; + } else if (ret != EAGAIN) { + DEBUG(SSSDBG_OP_FAILURE, "Invalid name received [%s]\n", rawinp); + ret = ERR_INPUT_PARSE; + goto done; + } + } + + /* EAGAIN - check the DP for subdomains */ + + DEBUG(SSSDBG_FUNC_DATA, "Requesting info for [%s] from [%s]\n", + state->name, state->domname ? state->domname : "<ALL>"); + + /* We explicitly use force=false here. This request should decide itself + * if it's time to re-use the cached subdomain list or refresh. If the + * caller needs to specify the 'force' parameter, they should use the + * sss_dp_get_domains_send() request itself + */ + subreq = sss_dp_get_domains_send(state, rctx, false, state->domname); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, sss_parse_inp_done, req); + return req; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, rctx->ev); + return req; +} + +static void sss_parse_inp_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct sss_parse_inp_state *state = tevent_req_data(req, + struct sss_parse_inp_state); + + ret = sss_dp_get_domains_recv(subreq); + talloc_free(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + state->error = ERR_OK; + + ret = sss_parse_name_for_domains(state, state->rctx->domains, + state->default_domain, + state->rawinp, + &state->domname, &state->name); + if (ret == EAGAIN && state->domname != NULL && state->name == NULL) { + DEBUG(SSSDBG_FUNC_DATA, + "Unknown domain in [%s]\n", state->rawinp); + state->error = ERR_DOMAIN_NOT_FOUND; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Invalid name received [%s]\n", state->rawinp); + state->error = ERR_INPUT_PARSE; + } + + if (state->error != ERR_OK) { + tevent_req_error(req, state->error); + return; + } + + /* Was able to parse the name now */ + tevent_req_done(req); +} + +errno_t sss_parse_inp_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + char **_name, char **_domname) +{ + struct sss_parse_inp_state *state = tevent_req_data(req, + struct sss_parse_inp_state); + + if (state->error != ERR_DOMAIN_NOT_FOUND) { + TEVENT_REQ_RETURN_ON_ERROR(req); + } + + if (_name) { + *_name = talloc_steal(mem_ctx, state->name); + } + + if (_domname) { + *_domname = talloc_steal(mem_ctx, state->domname); + } + + return state->error; +} + +/* ========== Get domain of an account ================= */ + + +struct sss_dp_get_account_domain_state { + uint16_t dp_error; + uint32_t error; + const char *domain_name; +}; + +static void sss_dp_get_account_domain_done(struct tevent_req *subreq); + +struct tevent_req * +sss_dp_get_account_domain_send(TALLOC_CTX *mem_ctx, + struct resp_ctx *rctx, + struct sss_domain_info *dom, + bool fast_reply, + enum sss_dp_acct_type type, + uint32_t opt_id, + const char *opt_str) +{ + struct sss_dp_get_account_domain_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + struct be_conn *be_conn; + uint32_t entry_type; + char *filter; + uint32_t dp_flags; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct sss_dp_get_account_domain_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + ret = sss_dp_get_domain_conn(rctx, dom->conn_name, &be_conn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "BUG: The Data Provider connection for %s is not available!\n", + dom->name); + ret = EIO; + goto done; + } + + switch (type) { + case SSS_DP_USER: + entry_type = BE_REQ_USER; + break; + case SSS_DP_GROUP: + entry_type = BE_REQ_GROUP; + break; + case SSS_DP_USER_AND_GROUP: + entry_type = BE_REQ_USER_AND_GROUP; + break; + case SSS_DP_SECID: + entry_type = BE_REQ_BY_SECID; + break; + default: + DEBUG(SSSDBG_OP_FAILURE, + "Unsupported lookup type %X for this request\n", type); + return NULL; + } + + if (type == SSS_DP_SECID) { + if (opt_str == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "BUG: SID search called without SID parameter!\n"); + ret = EINVAL; + goto done; + } + filter = talloc_asprintf(state, DP_SEC_ID"=%s", opt_str); + } else { + filter = talloc_asprintf(state, "idnumber=%u", opt_id); + } + if (filter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory?!\n"); + ret = ENOMEM; + goto done; + } + + dp_flags = fast_reply ? DP_FAST_REPLY : 0; + + subreq = sbus_call_dp_dp_getAccountDomain_send(state, be_conn->conn, + be_conn->bus_name, + SSS_BUS_PATH, dp_flags, + entry_type, filter, + sss_chain_id_get()); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sss_dp_get_account_domain_done, req); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, rctx->ev); + } + + return req; +} + +static void sss_dp_get_account_domain_done(struct tevent_req *subreq) +{ + struct sss_dp_get_account_domain_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sss_dp_get_account_domain_state); + + ret = sbus_call_dp_dp_getAccountDomain_recv(state, subreq, &state->dp_error, + &state->error, + &state->domain_name); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not get account info [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (state->dp_error != DP_ERR_OK) { + DEBUG(state->error == ERR_GET_ACCT_DOM_NOT_SUPPORTED ? SSSDBG_TRACE_INTERNAL + : SSSDBG_IMPORTANT_INFO, + "Data Provider Error: %u, %u [%s]\n", + (unsigned int)state->dp_error, (unsigned int)state->error, + sss_strerror(state->error)); + tevent_req_error(req, state->error ? state->error : EIO); + return; + } + + tevent_req_done(req); + return; +} + +errno_t sss_dp_get_account_domain_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + char **_domain) +{ + struct sss_dp_get_account_domain_state *state; + state = tevent_req_data(req, struct sss_dp_get_account_domain_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_domain = talloc_strdup(mem_ctx, state->domain_name); + if (*_domain == NULL) { + return ENOMEM; + } + + return EOK; +} diff --git a/src/responder/common/responder_iface.c b/src/responder/common/responder_iface.c new file mode 100644 index 0000000..cf32fe0 --- /dev/null +++ b/src/responder/common/responder_iface.c @@ -0,0 +1,161 @@ +/* + Copyright (C) 2016 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 "util/util.h" +#include "sss_iface/sss_iface_async.h" +#include "responder/common/negcache.h" +#include "responder/common/responder.h" + +static void set_domain_state_by_name(struct resp_ctx *rctx, + const char *domain_name, + enum sss_domain_state state) +{ + struct sss_domain_info *dom; + + if (domain_name == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "BUG: NULL domain name\n"); + return; + } + + DEBUG(SSSDBG_TRACE_LIBS, "Setting state of domain %s\n", domain_name); + + for (dom = rctx->domains; + dom != NULL; + dom = get_next_domain(dom, SSS_GND_ALL_DOMAINS)) { + + if (strcasecmp(dom->name, domain_name) == 0) { + break; + } + } + + if (dom != NULL) { + sss_domain_set_state(dom, state); + } +} + +static errno_t +sss_resp_domain_active(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct resp_ctx *rctx, + const char *domain_name) +{ + DEBUG(SSSDBG_TRACE_LIBS, "Enabling domain %s\n", domain_name); + + set_domain_state_by_name(rctx, domain_name, DOM_ACTIVE); + + return EOK; +} + +static errno_t +sss_resp_domain_inconsistent(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct resp_ctx *rctx, + const char *domain_name) +{ + DEBUG(SSSDBG_TRACE_LIBS, "Disabling domain %s\n", domain_name); + + set_domain_state_by_name(rctx, domain_name, DOM_INCONSISTENT); + + return EOK; +} + +static errno_t +sss_resp_reset_ncache_users(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct resp_ctx *rctx) +{ + sss_ncache_reset_users(rctx->ncache); + + return EOK; +} + +static errno_t +sss_resp_reset_ncache_groups(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct resp_ctx *rctx) +{ + sss_ncache_reset_groups(rctx->ncache); + + return EOK; +} + +errno_t +sss_resp_register_sbus_iface(struct sbus_connection *conn, + struct resp_ctx *rctx) +{ + errno_t ret; + + SBUS_INTERFACE(iface_resp_domain, + sssd_Responder_Domain, + SBUS_METHODS( + SBUS_SYNC(METHOD, sssd_Responder_Domain, SetActive, sss_resp_domain_active, rctx), + SBUS_SYNC(METHOD, sssd_Responder_Domain, SetInconsistent, sss_resp_domain_inconsistent, rctx) + ), + SBUS_SIGNALS(SBUS_NO_SIGNALS), + SBUS_PROPERTIES(SBUS_NO_PROPERTIES) + ); + + SBUS_INTERFACE(iface_resp_negcache, + sssd_Responder_NegativeCache, + SBUS_METHODS( + SBUS_SYNC(METHOD, sssd_Responder_NegativeCache, ResetUsers, sss_resp_reset_ncache_users, rctx), + SBUS_SYNC(METHOD, sssd_Responder_NegativeCache, ResetGroups, sss_resp_reset_ncache_groups, rctx) + ), + SBUS_SIGNALS(SBUS_NO_SIGNALS), + SBUS_PROPERTIES(SBUS_NO_PROPERTIES) + ); + + struct sbus_path paths[] = { + {SSS_BUS_PATH, &iface_resp_domain}, + {SSS_BUS_PATH, &iface_resp_negcache}, + {NULL, NULL} + }; + + ret = sbus_connection_add_path_map(conn, paths); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to add paths [%d]: %s\n", + ret, sss_strerror(ret)); + } + + return ret; +} + +errno_t +sss_resp_register_service_iface(struct resp_ctx *rctx) +{ + errno_t ret; + + SBUS_INTERFACE(iface_svc, + sssd_service, + SBUS_METHODS( + SBUS_SYNC(METHOD, sssd_service, rotateLogs, responder_logrotate, rctx) + ), + SBUS_SIGNALS(SBUS_NO_SIGNALS), + SBUS_PROPERTIES( + SBUS_SYNC(GETTER, sssd_service, debug_level, generic_get_debug_level, NULL), + SBUS_SYNC(SETTER, sssd_service, debug_level, generic_set_debug_level, NULL) + ) + ); + + ret = sbus_connection_add_path(rctx->mon_conn, SSS_BUS_PATH, &iface_svc); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to register service interface" + "[%d]: %s\n", ret, sss_strerror(ret)); + } + + return ret; +} diff --git a/src/responder/common/responder_packet.c b/src/responder/common/responder_packet.c new file mode 100644 index 0000000..db74cc1 --- /dev/null +++ b/src/responder/common/responder_packet.c @@ -0,0 +1,364 @@ +/* + SSSD + + SSS Client Responder, command parser + + Copyright (C) Simo Sorce <ssorce@redhat.com> 2008 + + 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 <sys/types.h> +#include <sys/socket.h> +#include <string.h> +#include <errno.h> +#include <talloc.h> + +#include "util/util.h" +#include "responder/common/responder_packet.h" + +#define SSSSRV_PACKET_MEM_SIZE 512 + +struct sss_packet { + size_t memsize; + + /* Structure of the buffer: + * Bytes Content + * --------------------------------- + * 0-15 packet header + * 0-3 packet length (uint32_t) + * 4-7 command type (uint32_t) + * 8-11 status (uint32_t) + * 12-15 reserved + * 16+ packet body */ + uint8_t *buffer; + + /* io pointer */ + size_t iop; +}; + +/* Offsets to data in sss_packet's buffer */ +#define SSS_PACKET_LEN_OFFSET 0 +#define SSS_PACKET_CMD_OFFSET sizeof(uint32_t) +#define SSS_PACKET_ERR_OFFSET (2*(sizeof(uint32_t))) +#define SSS_PACKET_BODY_OFFSET (4*(sizeof(uint32_t))) + +static void sss_packet_set_len(struct sss_packet *packet, uint32_t len); +static void sss_packet_set_cmd(struct sss_packet *packet, + enum sss_cli_command cmd); +static uint32_t sss_packet_get_len(struct sss_packet *packet); + +/* + * Allocate a new packet structure + * + * - if size is defined use it otherwise the default packet will be + * SSSSRV_PACKET_MEM_SIZE bytes. + */ +int sss_packet_new(TALLOC_CTX *mem_ctx, size_t size, + enum sss_cli_command cmd, + struct sss_packet **rpacket) +{ + struct sss_packet *packet; + + packet = talloc(mem_ctx, struct sss_packet); + if (!packet) return ENOMEM; + + if (size) { + int n = (size + SSS_NSS_HEADER_SIZE) / SSSSRV_PACKET_MEM_SIZE; + packet->memsize = (n + 1) * SSSSRV_PACKET_MEM_SIZE; + } else { + packet->memsize = SSSSRV_PACKET_MEM_SIZE; + } + + packet->buffer = talloc_size(packet, packet->memsize); + if (!packet->buffer) { + talloc_free(packet); + return ENOMEM; + } + memset(packet->buffer, 0, SSS_NSS_HEADER_SIZE); + + sss_packet_set_len(packet, size + SSS_NSS_HEADER_SIZE); + sss_packet_set_cmd(packet, cmd); + + packet->iop = 0; + + *rpacket = packet; + + return EOK; +} + +/* grows a packet size only in SSSSRV_PACKET_MEM_SIZE chunks */ +int sss_packet_grow(struct sss_packet *packet, size_t size) +{ + size_t totlen, len; + uint8_t *newmem; + uint32_t packet_len; + + if (size == 0) { + return EOK; + } + + totlen = packet->memsize; + packet_len = sss_packet_get_len(packet); + + len = packet_len + size; + + /* make sure we do not overflow */ + if (totlen < len) { + int n = len / SSSSRV_PACKET_MEM_SIZE + 1; + totlen += n * SSSSRV_PACKET_MEM_SIZE; + if (totlen < len) { + return EINVAL; + } + } + + if (totlen > packet->memsize) { + newmem = talloc_realloc_size(packet, packet->buffer, totlen); + if (!newmem) { + return ENOMEM; + } + + packet->memsize = totlen; + + /* re-set pointers if realloc had to move memory */ + if (newmem != packet->buffer) { + packet->buffer = newmem; + } + } + + packet_len += size; + sss_packet_set_len(packet, packet_len); + + + return 0; +} + +/* reclaim back previously reserved space in the packet + * usually done in function recovering from not fatal errors */ +int sss_packet_shrink(struct sss_packet *packet, size_t size) +{ + size_t newlen; + size_t oldlen = sss_packet_get_len(packet); + + if (size > oldlen) return EINVAL; + + newlen = oldlen - size; + if (newlen < SSS_NSS_HEADER_SIZE) return EINVAL; + + sss_packet_set_len(packet, newlen); + return 0; +} + +int sss_packet_set_size(struct sss_packet *packet, size_t size) +{ + size_t newlen; + + newlen = SSS_NSS_HEADER_SIZE + size; + + /* make sure we do not overflow */ + if (packet->memsize < newlen) return EINVAL; + + sss_packet_set_len(packet, newlen); + + return 0; +} + +int sss_packet_recv(struct sss_packet *packet, int fd) +{ + size_t rb; + size_t len; + void *buf; + size_t new_len; + int ret; + + buf = (uint8_t *)packet->buffer + packet->iop; + if (packet->iop >= SSS_PACKET_CMD_OFFSET) { + len = sss_packet_get_len(packet) - packet->iop; + } else { + len = packet->memsize - packet->iop; + } + + /* check for wrapping */ + if (len > (packet->memsize - packet->iop)) { + return EINVAL; + } + + errno = 0; + rb = recv(fd, buf, len, 0); + + if (rb == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { + return EAGAIN; + } else { + return errno; + } + } + + if (rb == 0) { + return ENODATA; + } + + packet->iop += rb; + if (packet->iop < SSS_PACKET_CMD_OFFSET) { + return EAGAIN; + } + + new_len = sss_packet_get_len(packet); + if (new_len > packet->memsize) { + enum sss_cli_command cmd = sss_packet_get_cmd(packet); + size_t max_recv_size; + + /* Allow certain packet types to use a larger buffer. */ + switch (cmd) { + case SSS_NSS_GETNAMEBYCERT: + case SSS_NSS_GETLISTBYCERT: + max_recv_size = SSS_CERT_PACKET_MAX_RECV_SIZE; + break; + + case SSS_GSSAPI_SEC_CTX: + case SSS_PAC_ADD_PAC_USER: + max_recv_size = SSS_GSSAPI_PACKET_MAX_RECV_SIZE; + break; + + default: + max_recv_size = 0; + } + + /* Due to the way sss_packet_grow() works, the packet len must be set + * to 0 first, and then grown to the expected size. */ + if (new_len <= max_recv_size) { + sss_packet_set_len(packet, 0); + ret = sss_packet_grow(packet, new_len); + if (ret != EOK) { + return ret; + } + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Refusing to read overlarge packet from fd %d (length %zu bytes, cmd %#04x)", + fd, new_len, cmd); + return EINVAL; + } + } + + if (packet->iop < new_len) { + return EAGAIN; + } + + return EOK; +} + +int sss_packet_send(struct sss_packet *packet, int fd) +{ + size_t rb; + size_t len; + void *buf; + + if (!packet) { + /* No packet object to write to? */ + return EINVAL; + } + + buf = packet->buffer + packet->iop; + len = sss_packet_get_len(packet) - packet->iop; + + errno = 0; + rb = send(fd, buf, len, 0); + + if (rb == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { + return EAGAIN; + } else { + return errno; + } + } + + if (rb == 0) { + return EIO; + } + + packet->iop += rb; + + if (packet->iop < sss_packet_get_len(packet)) { + return EAGAIN; + } + + return EOK; +} + +enum sss_cli_command sss_packet_get_cmd(struct sss_packet *packet) +{ + uint32_t cmd; + + SAFEALIGN_COPY_UINT32(&cmd, packet->buffer + SSS_PACKET_CMD_OFFSET, NULL); + return (enum sss_cli_command)cmd; +} + +uint32_t sss_packet_get_status(struct sss_packet *packet) +{ + uint32_t status; + + SAFEALIGN_COPY_UINT32(&status, packet->buffer + SSS_PACKET_ERR_OFFSET, + NULL); + return status; +} + +void sss_packet_get_body(struct sss_packet *packet, uint8_t **body, size_t *blen) +{ + *body = packet->buffer + SSS_PACKET_BODY_OFFSET; + *blen = sss_packet_get_len(packet) - SSS_NSS_HEADER_SIZE; +} + +errno_t sss_packet_set_body(struct sss_packet *packet, + uint8_t *body, + size_t blen) +{ + uint8_t *pbody; + size_t plen; + errno_t ret; + + ret = sss_packet_grow(packet, blen); + if (ret != EOK) { + return ret; + } + + sss_packet_get_body(packet, &pbody, &plen); + memcpy(pbody, body, blen); + + return EOK; +} + +void sss_packet_set_error(struct sss_packet *packet, int error) +{ + SAFEALIGN_SETMEM_UINT32(packet->buffer + SSS_PACKET_ERR_OFFSET, error, + NULL); +} + +static void sss_packet_set_len(struct sss_packet *packet, uint32_t len) +{ + SAFEALIGN_SETMEM_UINT32(packet->buffer + SSS_PACKET_LEN_OFFSET, len, NULL); +} + +static void sss_packet_set_cmd(struct sss_packet *packet, + enum sss_cli_command cmd) +{ + SAFEALIGN_SETMEM_UINT32(packet->buffer + SSS_PACKET_CMD_OFFSET, cmd, NULL); +} + +static uint32_t sss_packet_get_len(struct sss_packet *packet) +{ + uint32_t len; + + SAFEALIGN_COPY_UINT32(&len, packet->buffer + SSS_PACKET_LEN_OFFSET, NULL); + return len; +} diff --git a/src/responder/common/responder_packet.h b/src/responder/common/responder_packet.h new file mode 100644 index 0000000..fd99196 --- /dev/null +++ b/src/responder/common/responder_packet.h @@ -0,0 +1,51 @@ +/* + SSSD + + SSS Client Responder, header file + + Copyright (C) Simo Sorce <ssorce@redhat.com> 2008 + + 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/>. +*/ + +#ifndef __SSSSRV_PACKET_H__ +#define __SSSSRV_PACKET_H__ + +#include "sss_client/sss_cli.h" + +#define SSS_PACKET_MAX_RECV_SIZE 1024 +#define SSS_CERT_PACKET_MAX_RECV_SIZE ( 10 * SSS_PACKET_MAX_RECV_SIZE ) +#define SSS_GSSAPI_PACKET_MAX_RECV_SIZE ( 128 * 1024 ) + +struct sss_packet; + +int sss_packet_new(TALLOC_CTX *mem_ctx, size_t size, + enum sss_cli_command cmd, + struct sss_packet **rpacket); +int sss_packet_grow(struct sss_packet *packet, size_t size); +int sss_packet_shrink(struct sss_packet *packet, size_t size); +int sss_packet_set_size(struct sss_packet *packet, size_t size); +int sss_packet_recv(struct sss_packet *packet, int fd); +int sss_packet_send(struct sss_packet *packet, int fd); +enum sss_cli_command sss_packet_get_cmd(struct sss_packet *packet); +uint32_t sss_packet_get_status(struct sss_packet *packet); +void sss_packet_get_body(struct sss_packet *packet, uint8_t **body, size_t *blen); +void sss_packet_set_error(struct sss_packet *packet, int error); + +/* Grow packet and set its body. */ +errno_t sss_packet_set_body(struct sss_packet *packet, + uint8_t *body, + size_t blen); + +#endif /* __SSSSRV_PACKET_H__ */ diff --git a/src/responder/common/responder_utils.c b/src/responder/common/responder_utils.c new file mode 100644 index 0000000..47aeace --- /dev/null +++ b/src/responder/common/responder_utils.c @@ -0,0 +1,527 @@ + +/* + SSSD + + Common Responder utility functions + + Copyright (C) Sumit Bose <sbose@redhat.com> 2014 + + 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 <talloc.h> + +#include "db/sysdb.h" +#include "responder/common/responder.h" +#include "responder/common/cache_req/cache_req.h" +#include "util/util.h" + +static inline bool +attr_in_list(const char **list, size_t nlist, const char *str) +{ + return string_in_list_size(str, list, nlist, false); +} + +const char **parse_attr_list_ex(TALLOC_CTX *mem_ctx, const char *conf_str, + const char **defaults) +{ + TALLOC_CTX *tmp_ctx; + errno_t ret; + const char **list = NULL; + const char **res = NULL; + int list_size; + char **conf_list = NULL; + int conf_list_size = 0; + const char **allow = NULL; + const char **deny = NULL; + int ai = 0, di = 0, li = 0; + int i; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return NULL; + } + + if (conf_str) { + ret = split_on_separator(tmp_ctx, conf_str, ',', true, true, + &conf_list, &conf_list_size); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot parse attribute ACL list %s: %d\n", conf_str, ret); + goto done; + } + + allow = talloc_zero_array(tmp_ctx, const char *, conf_list_size); + deny = talloc_zero_array(tmp_ctx, const char *, conf_list_size); + if (allow == NULL || deny == NULL) { + goto done; + } + } + + for (i = 0; i < conf_list_size; i++) { + switch (conf_list[i][0]) { + case '+': + allow[ai] = conf_list[i] + 1; + ai++; + continue; + case '-': + deny[di] = conf_list[i] + 1; + di++; + continue; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "ACL values must start with " + "either '+' (allow) or '-' (deny), got '%s'\n", + conf_list[i]); + goto done; + } + } + + /* Assume the output will have to hold defaults and all the configured, + * values, resize later + */ + list_size = 0; + if (defaults != NULL) { + while (defaults[list_size]) { + list_size++; + } + } + list_size += conf_list_size; + + list = talloc_zero_array(tmp_ctx, const char *, list_size + 1); + if (list == NULL) { + goto done; + } + + /* Start by copying explicitly allowed attributes */ + for (i = 0; i < ai; i++) { + /* if the attribute is explicitly denied, skip it */ + if (attr_in_list(deny, di, allow[i])) { + continue; + } + + /* If the attribute is already in the list, skip it */ + if (attr_in_list(list, li, allow[i])) { + continue; + } + + list[li] = talloc_strdup(list, allow[i]); + if (list[li] == NULL) { + goto done; + } + li++; + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Added allowed attr %s to whitelist\n", allow[i]); + } + + /* Add defaults */ + if (defaults != NULL) { + for (i = 0; defaults[i]; i++) { + /* if the attribute is explicitly denied, skip it */ + if (attr_in_list(deny, di, defaults[i])) { + continue; + } + + /* If the attribute is already in the list, skip it */ + if (attr_in_list(list, li, defaults[i])) { + continue; + } + + list[li] = talloc_strdup(list, defaults[i]); + if (list[li] == NULL) { + goto done; + } + li++; + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Added default attr %s to whitelist\n", defaults[i]); + } + } + + res = talloc_steal(mem_ctx, list); +done: + talloc_free(tmp_ctx); + return res; +} + +char *sss_resp_create_fqname(TALLOC_CTX *mem_ctx, + struct resp_ctx *rctx, + struct sss_domain_info *dom, + bool name_is_upn, + const char *orig_name) +{ + TALLOC_CTX *tmp_ctx; + char *name; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return NULL; + } + + name = sss_get_cased_name(tmp_ctx, orig_name, dom->case_sensitive); + if (name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_get_cased_name failed\n"); + talloc_free(tmp_ctx); + return NULL; + } + + name = sss_reverse_replace_space(tmp_ctx, name, rctx->override_space); + if (name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_reverse_replace_space failed\n"); + talloc_free(tmp_ctx); + return NULL; + } + + + if (name_is_upn == false) { + name = sss_create_internal_fqname(tmp_ctx, name, dom->name); + if (name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_create_internal_fqname failed\n"); + talloc_free(tmp_ctx); + return NULL; + } + } + + name = talloc_steal(mem_ctx, name); + talloc_free(tmp_ctx); + return name; +} + +struct resp_resolve_group_names_state { + struct tevent_context *ev; + struct resp_ctx *rctx; + struct sss_domain_info *dom; + struct ldb_result *initgr_res; + + bool needs_refresh; + unsigned int group_iter; + bool is_original_primary_group_request; + + struct ldb_result *initgr_named_res; +}; + +static void resp_resolve_group_done(struct tevent_req *subreq); +static errno_t resp_resolve_group_next(struct tevent_req *req); +static errno_t resp_resolve_group_trigger_request(struct tevent_req *req, const char *attr_name); +static errno_t resp_resolve_group_reread_names(struct resp_resolve_group_names_state *state); + +struct tevent_req *resp_resolve_group_names_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_domain_info *dom, + struct ldb_result *initgr_res) +{ + struct resp_resolve_group_names_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct resp_resolve_group_names_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + state->ev = ev; + state->rctx = rctx; + state->dom = dom; + state->initgr_res = initgr_res; + state->is_original_primary_group_request = true; + + ret = resp_resolve_group_next(req); + if (ret == EOK) { + goto immediate; + } else if (ret != EAGAIN) { + goto immediate; + } + + return req; + +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static bool +resp_resolve_group_needs_refresh(struct resp_resolve_group_names_state *state) +{ + /* Refresh groups that have a non-zero GID, + * but are marked as non-POSIX + */ + bool is_posix; + uint64_t gid; + struct ldb_message *group_msg; + + group_msg = state->initgr_res->msgs[state->group_iter]; + + is_posix = ldb_msg_find_attr_as_bool(group_msg, SYSDB_POSIX, false); + gid = ldb_msg_find_attr_as_uint64(group_msg, SYSDB_GIDNUM, 0); + + if (is_posix == false && gid != 0) { + return true; + } + + return false; +} + +static errno_t resp_resolve_group_next(struct tevent_req *req) +{ + struct resp_resolve_group_names_state *state; + errno_t ret; + + state = tevent_req_data(req, struct resp_resolve_group_names_state); + + while (state->group_iter < state->initgr_res->count + && !resp_resolve_group_needs_refresh(state)) { + state->group_iter++; + } + + if (state->group_iter >= state->initgr_res->count) { + /* All groups were refreshed */ + return EOK; + } + + if(state->group_iter == 0 && + state->is_original_primary_group_request == true) { + ret = resp_resolve_group_trigger_request(req, + SYSDB_PRIMARY_GROUP_GIDNUM); + + /* If auto_private_groups is disabled then + * resp_resolve_group_trigger_request will return EINVAL, but this + * doesn't mean a failure. Thus, the search should continue with the + * next element. + */ + if(ret == EINVAL) { + state->is_original_primary_group_request = false; + return resp_resolve_group_trigger_request(req, SYSDB_GIDNUM); + } else { + return ret; + } + } else { + return resp_resolve_group_trigger_request(req, SYSDB_GIDNUM); + } +} + +static errno_t resp_resolve_group_trigger_request(struct tevent_req *req, + const char *attr_name) +{ + struct cache_req_data *data; + uint64_t gid; + struct tevent_req *subreq; + struct resp_resolve_group_names_state *state; + + state = tevent_req_data(req, struct resp_resolve_group_names_state); + + gid = ldb_msg_find_attr_as_uint64(state->initgr_res->msgs[state->group_iter], + attr_name, 0); + if (gid == 0) { + return EINVAL; + } + + data = cache_req_data_id_attrs(state, CACHE_REQ_GROUP_BY_ID, gid, NULL); + if (data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to set cache request data!\n"); + return ENOMEM; + } + + subreq = cache_req_send(state, + state->ev, + state->rctx, + state->rctx->ncache, + 0, + CACHE_REQ_ANY_DOM, + NULL, + data); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to send cache request!\n"); + return ENOMEM; + } + + tevent_req_set_callback(subreq, resp_resolve_group_done, req); + return EAGAIN; +} + +static void resp_resolve_group_done(struct tevent_req *subreq) +{ + struct resp_resolve_group_names_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct resp_resolve_group_names_state); + + ret = cache_req_single_domain_recv(state, subreq, NULL); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to refresh group\n"); + /* Try to refresh the others on error */ + } + + if(state->group_iter == 0 && + state->is_original_primary_group_request == true) { + state->is_original_primary_group_request = false; + } else { + state->group_iter++; + } + state->needs_refresh = true; + + ret = resp_resolve_group_next(req); + if (ret == EOK) { + ret = resp_resolve_group_reread_names(state); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + DEBUG(SSSDBG_TRACE_FUNC, "All groups are refreshed, done\n"); + tevent_req_done(req); + return; + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + return; + } + + /* Continue refreshing.. */ +} + +static errno_t +resp_resolve_group_reread_names(struct resp_resolve_group_names_state *state) +{ + errno_t ret; + const char *username; + + /* re-read reply in case any groups were renamed */ + /* msgs[0] is the user entry */ + username = sss_view_ldb_msg_find_attr_as_string(state->dom, + state->initgr_res->msgs[0], + SYSDB_NAME, + NULL); + if (username == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "A user with no name?\n"); + return EINVAL; + } + + ret = sysdb_initgroups_with_views(state, + state->dom, + username, + &state->initgr_named_res); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot re-read the group names\n"); + return ret; + } + + return EOK; +} + +int resp_resolve_group_names_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ldb_result **_initgr_named_res) +{ + struct resp_resolve_group_names_state *state = NULL; + state = tevent_req_data(req, struct resp_resolve_group_names_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_initgr_named_res = talloc_steal(mem_ctx, state->initgr_named_res); + return EOK; +} + +const char * +sss_resp_get_shell_override(struct ldb_message *msg, + struct resp_ctx *rctx, + struct sss_domain_info *domain) +{ + const char *shell; + int i; + + /* Here we skip the files provider as it should always return *only* + * what's in the files and nothing else. */ + if (!is_files_provider(domain)) { + /* Check whether we are unconditionally overriding + * the server for the login shell. */ + if (domain->override_shell) { + return domain->override_shell; + } else if (rctx->override_shell) { + return rctx->override_shell; + } + } + + shell = sss_view_ldb_msg_find_attr_as_string(domain, msg, SYSDB_SHELL, + NULL); + if (shell == NULL) { + /* Check whether there is a default shell specified */ + if (domain->default_shell) { + return domain->default_shell; + } else if (rctx->default_shell) { + return rctx->default_shell; + } + + return ""; + } + + if (rctx->allowed_shells == NULL && rctx->vetoed_shells == NULL) { + return shell; + } + + if (rctx->vetoed_shells) { + for (i = 0; rctx->vetoed_shells[i]; i++) { + if (strcmp(rctx->vetoed_shells[i], shell) == 0) { + DEBUG(SSSDBG_FUNC_DATA, + "The shell '%s' is vetoed. Using fallback.\n", + shell); + return rctx->shell_fallback; + } + } + } + + if (rctx->etc_shells) { + for (i = 0; rctx->etc_shells[i]; i++) { + if (strcmp(shell, rctx->etc_shells[i]) == 0) { + DEBUG(SSSDBG_TRACE_ALL, + "Shell %s found in /etc/shells\n", shell); + break; + } + } + + if (rctx->etc_shells[i]) { + DEBUG(SSSDBG_TRACE_ALL, "Using original shell '%s'\n", shell); + return shell; + } + } + + if (rctx->allowed_shells) { + if (strcmp(rctx->allowed_shells[0], "*") == 0) { + DEBUG(SSSDBG_FUNC_DATA, + "The shell '%s' is allowed but does not exist. " + "Using fallback\n", shell); + return rctx->shell_fallback; + } else { + for (i = 0; rctx->allowed_shells[i]; i++) { + if (strcmp(rctx->allowed_shells[i], shell) == 0) { + DEBUG(SSSDBG_FUNC_DATA, + "The shell '%s' is allowed but does not exist. " + "Using fallback\n", shell); + return rctx->shell_fallback; + } + } + } + } + + DEBUG(SSSDBG_FUNC_DATA, + "The shell '%s' is not allowed and does not exist.\n", shell); + + return NOLOGIN_SHELL; +} diff --git a/src/responder/ifp/ifp_cache.c b/src/responder/ifp/ifp_cache.c new file mode 100644 index 0000000..a4dd393 --- /dev/null +++ b/src/responder/ifp/ifp_cache.c @@ -0,0 +1,270 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2015 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 <talloc.h> +#include <tevent.h> + +#include "db/sysdb.h" +#include "util/util.h" +#include "responder/common/responder.h" +#include "responder/ifp/ifp_cache.h" +#include "responder/ifp/ifp_users.h" +#include "responder/ifp/ifp_groups.h" + +static struct ldb_dn * +ifp_cache_build_base_dn(TALLOC_CTX *mem_ctx, + enum ifp_cache_type type, + struct sss_domain_info *domain) +{ + struct ldb_dn *base_dn = NULL; + + switch (type) { + case IFP_CACHE_USER: + base_dn = sysdb_user_base_dn(mem_ctx, domain); + break; + case IFP_CACHE_GROUP: + base_dn = sysdb_group_base_dn(mem_ctx, domain); + break; + } + + return base_dn; +} + +static char * +ifp_cache_build_path(TALLOC_CTX *mem_ctx, + enum ifp_cache_type type, + struct sss_domain_info *domain, + struct ldb_message *msg) +{ + char *path = NULL; + + switch (type) { + case IFP_CACHE_USER: + path = ifp_users_build_path_from_msg(mem_ctx, domain, msg); + break; + case IFP_CACHE_GROUP: + path = ifp_groups_build_path_from_msg(mem_ctx, domain, msg); + break; + } + + return path; +} + +static const char * +ifp_cache_object_class(enum ifp_cache_type type) +{ + const char *class = NULL; + + switch (type) { + case IFP_CACHE_USER: + class = SYSDB_USER_CLASS; + break; + case IFP_CACHE_GROUP: + class = SYSDB_GROUP_CLASS; + break; + } + + return class; +} + +static errno_t +ifp_cache_get_cached_objects(TALLOC_CTX *mem_ctx, + enum ifp_cache_type type, + struct sss_domain_info *domain, + const char ***_paths) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_dn *base_dn; + struct ldb_result *result; + const char *class = ifp_cache_object_class(type); + const char **paths; + errno_t ret; + int ldb_ret; + int i; + const char *attrs[] = {SYSDB_OBJECTCATEGORY, SYSDB_UIDNUM, + SYSDB_GIDNUM, NULL}; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + base_dn = ifp_cache_build_base_dn(tmp_ctx, type, domain); + if (base_dn == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create base dn\n"); + ret = ENOMEM; + goto done; + } + + ldb_ret = ldb_search(sysdb_ctx_get_ldb(domain->sysdb), tmp_ctx, &result, + base_dn, LDB_SCOPE_SUBTREE, attrs, + "(&(%s=%s)(%s=TRUE))", SYSDB_OBJECTCATEGORY, class, + SYSDB_IFP_CACHED); + if (ldb_ret != LDB_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to search the cache\n"); + ret = sss_ldb_error_to_errno(ldb_ret); + goto done; + } + + paths = talloc_zero_array(tmp_ctx, const char *, result->count + 1); + if (paths == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = 0; i < result->count; i++) { + paths[i] = ifp_cache_build_path(paths, type, domain, result->msgs[i]); + if (paths[i] == NULL) { + ret = ENOMEM; + goto done; + } + } + + *_paths = talloc_steal(mem_ctx, paths); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t +ifp_cache_list_domains(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domains, + enum ifp_cache_type type, + const char ***_paths) +{ + TALLOC_CTX *tmp_ctx; + struct sss_domain_info *domain; + const char **tmp_paths = NULL; + const char **paths; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + domain = domains; + paths = NULL; + while (domain != NULL) { + ret = ifp_cache_get_cached_objects(tmp_ctx, type, domain, &tmp_paths); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to build object list " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + ret = add_strings_lists(tmp_ctx, paths, tmp_paths, true, &paths); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to build object list " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + domain = get_next_domain(domain, SSS_GND_DESCEND); + } + + if (_paths != NULL) { + *_paths = talloc_steal(mem_ctx, paths); + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t +ifp_cache_list(TALLOC_CTX *mem_ctx, + struct ifp_ctx *ifp_ctx, + enum ifp_cache_type type, + const char ***_paths) +{ + return ifp_cache_list_domains(mem_ctx, ifp_ctx->rctx->domains, + type, _paths); +} + +errno_t +ifp_cache_list_by_domain(TALLOC_CTX *mem_ctx, + struct ifp_ctx *ifp_ctx, + const char *domainname, + enum ifp_cache_type type, + const char ***_paths) +{ + struct sss_domain_info *domain; + + domain = find_domain_by_name(ifp_ctx->rctx->domains, domainname, true); + if (domain == NULL) { + return ERR_DOMAIN_NOT_FOUND; + } + + return ifp_cache_get_cached_objects(mem_ctx, type, domain, _paths); +} + +static errno_t ifp_cache_object_set(struct sss_domain_info *domain, + struct ldb_dn *dn, + bool value) +{ + struct sysdb_attrs *attrs; + errno_t ret; + + attrs = sysdb_new_attrs(NULL); + if (attrs == NULL) { + return ENOMEM; + } + + ret = sysdb_attrs_add_bool(attrs, SYSDB_IFP_CACHED, value); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to add attribute [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = sysdb_set_entry_attr(domain->sysdb, dn, attrs, SYSDB_MOD_REP); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to modify entry [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; + +done: + talloc_free(attrs); + + return ret; +} + +errno_t +ifp_cache_object_store(struct sss_domain_info *domain, + struct ldb_dn *dn) +{ + return ifp_cache_object_set(domain, dn, true); +} + +errno_t +ifp_cache_object_remove(struct sss_domain_info *domain, + struct ldb_dn *dn) +{ + return ifp_cache_object_set(domain, dn, false); +} diff --git a/src/responder/ifp/ifp_cache.h b/src/responder/ifp/ifp_cache.h new file mode 100644 index 0000000..5c05a7f --- /dev/null +++ b/src/responder/ifp/ifp_cache.h @@ -0,0 +1,63 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2015 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/>. +*/ + +#ifndef IFP_CACHE_H_ +#define IFP_CACHE_H_ + +#include "confdb/confdb.h" +#include "responder/common/responder.h" +#include "responder/ifp/ifp_private.h" + +enum ifp_cache_type { + IFP_CACHE_USER, + IFP_CACHE_GROUP +}; + +errno_t +ifp_cache_list_domains(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domains, + enum ifp_cache_type type, + const char ***_paths); + +/* org.freedesktop-sssd-infopipe.Cache */ + +errno_t +ifp_cache_list(TALLOC_CTX *mem_ctx, + struct ifp_ctx *ifp_ctx, + enum ifp_cache_type type, + const char ***_paths); + +errno_t +ifp_cache_list_by_domain(TALLOC_CTX *mem_ctx, + struct ifp_ctx *ifp_ctx, + const char *domainname, + enum ifp_cache_type type, + const char ***_paths); + +/* org.freedesktop-sssd-infopipe.Cache.Object */ + +errno_t +ifp_cache_object_store(struct sss_domain_info *domain, + struct ldb_dn *dn); + +errno_t +ifp_cache_object_remove(struct sss_domain_info *domain, + struct ldb_dn *dn); +#endif /* IFP_CACHE_H_ */ diff --git a/src/responder/ifp/ifp_components.c b/src/responder/ifp/ifp_components.c new file mode 100644 index 0000000..c6b9837 --- /dev/null +++ b/src/responder/ifp/ifp_components.c @@ -0,0 +1,662 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2014 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 <string.h> +#include <talloc.h> +#include <signal.h> +#include <errno.h> +#include <utime.h> + +#include "config.h" +#include "confdb/confdb.h" +#include "util/util.h" +#include "responder/common/responder.h" +#include "responder/ifp/ifp_components.h" + +#define PATH_MONITOR IFP_PATH_COMPONENTS "/monitor" +#define PATH_RESPONDERS IFP_PATH_COMPONENTS "/Responders" +#define PATH_BACKENDS IFP_PATH_COMPONENTS "/Backends" + +enum component_type { + COMPONENT_MONITOR, + COMPONENT_RESPONDER, + COMPONENT_BACKEND +}; + +static bool responder_exists(const char *name) +{ + const char * const *svc = get_known_services(); + int i; + + for (i = 0; svc[i] != NULL; i++) { + if (strcmp(svc[i], name) == 0) { + return true; + } + } + + return false; +} + +static bool backend_exists(struct confdb_ctx *confdb, const char *name) +{ + char **names = NULL; + errno_t ret; + int i; + + ret = confdb_list_all_domain_names(NULL, confdb, &names); + if (ret != EOK) { + return false; + } + + for (i = 0; names[i] != NULL; i++) { + if (strcmp(names[i], name) == 0) { + return true; + } + } + + return false; +} + +static errno_t check_and_get_component_from_path(TALLOC_CTX *mem_ctx, + struct confdb_ctx *confdb, + const char *path, + enum component_type *_type, + char **_name) +{ + enum component_type type; + char *name = NULL; + errno_t ret; + + if (confdb == NULL || path == NULL) { + return EINVAL; + } + + if (strcmp(path, PATH_MONITOR) == 0) { + type = COMPONENT_MONITOR; + name = talloc_strdup(mem_ctx, "monitor"); + if (name == NULL) { + ret = ENOMEM; + goto done; + } + } else { + name = sbus_opath_object_name(mem_ctx, path, PATH_RESPONDERS); + if (name != NULL) { + type = COMPONENT_RESPONDER; + } else { + name = sbus_opath_object_name(mem_ctx, path, PATH_BACKENDS); + if (name != NULL) { + type = COMPONENT_BACKEND; + } else { + ret = EINVAL; + goto done; + } + } + } + + if (strchr(name, '/') != NULL) { + ret = EINVAL; + goto done; + } + + switch (type) { + case COMPONENT_MONITOR: + /* noop */ + break; + case COMPONENT_RESPONDER: + if (!responder_exists(name)) { + ret = ENOENT; + goto done; + } + break; + case COMPONENT_BACKEND: + if (!backend_exists(confdb, name)) { + ret = ENOENT; + goto done; + } + break; + } + + if (_type != NULL) { + *_type = type; + } + + if (_name != NULL) { + *_name = name; + } + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(name); + } + + return ret; +} + +static errno_t list_responders(TALLOC_CTX *mem_ctx, + const char ***_list, + int *_num) +{ + const char **list = NULL; + const char * const *svc = get_known_services(); + errno_t ret; + int num; + int i; + + for (num = 0; svc[num] != NULL; num++); + + list = talloc_zero_array(mem_ctx, const char*, num + 1); + if (list == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = 0; i < num; i++) { + list[i] = sbus_opath_compose(list, PATH_RESPONDERS, svc[i]); + if (list[i] == NULL) { + ret = ENOMEM; + goto done; + } + } + + *_num = num; + *_list = list; + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(list); + } + + return ret; +} + +static errno_t list_backends(TALLOC_CTX *mem_ctx, + struct confdb_ctx *confdb, + const char ***_list, + int *_num) +{ + TALLOC_CTX *tmp_ctx = NULL; + const char **list = NULL; + char **names = NULL; + errno_t ret; + int num; + int i; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = confdb_list_all_domain_names(tmp_ctx, confdb, &names); + if (ret != EOK) { + goto done; + } + + for (num = 0; names[num] != NULL; num++); + + list = talloc_zero_array(tmp_ctx, const char*, num + 1); + if (list == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = 0; i < num; i++) { + list[i] = sbus_opath_compose(list, PATH_BACKENDS, names[i]); + if (list[i] == NULL) { + ret = ENOMEM; + goto done; + } + } + + *_num = num; + *_list = talloc_steal(mem_ctx, list); + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t +ifp_list_components(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char ***_paths) +{ + TALLOC_CTX *tmp_ctx; + const char **responders; + const char **backends; + const char **result; + int num_responders; + int num_backends; + int num; + int i; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + ret = list_responders(tmp_ctx, &responders, &num_responders); + if (ret != EOK) { + goto done; + } + + ret = list_backends(tmp_ctx, ctx->rctx->cdb, &backends, &num_backends); + if (ret != EOK) { + goto done; + } + + num = num_responders + num_backends + 1; + result = talloc_zero_array(mem_ctx, const char *, num + 1); + if (result == NULL) { + ret = ENOMEM; + goto done; + } + + result[0] = PATH_MONITOR; + + for (i = 0; i < num_responders; i++) { + result[i + 1] = talloc_steal(result, responders[i]); + } + + for (i = 0; i < num_backends; i++) { + result[i + num_responders + 1] = talloc_steal(result, backends[i]); + } + + *_paths = result; + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + + +errno_t +ifp_list_responders(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char ***_paths) +{ + const char **result; + int num; + errno_t ret; + + ret = list_responders(mem_ctx, &result, &num); + if (ret != EOK) { + return ret; + } + + *_paths = result; + + return EOK; +} + +errno_t +ifp_list_backends(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char ***_paths) +{ + const char **result; + int num; + errno_t ret; + + ret = list_backends(mem_ctx, ctx->rctx->cdb, &result, &num); + if (ret != EOK) { + return ret; + } + + *_paths = result; + + return EOK; +} + +errno_t +ifp_find_monitor(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char **_path) +{ + *_path = PATH_MONITOR; + + return EOK; +} + + +errno_t +ifp_find_responder_by_name(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char *name, + const char **_path) +{ + const char *result; + + if (responder_exists(name)) { + result = sbus_opath_compose(mem_ctx, PATH_RESPONDERS, name); + if (result == NULL) { + return ENOMEM; + } + + *_path = result; + return EOK; + } + + DEBUG(SSSDBG_MINOR_FAILURE, "Responder \"%s\" does not exist", name); + return ENOENT; +} + +errno_t +ifp_find_backend_by_name(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char *name, + const char **_path) +{ + const char *result; + + if (backend_exists(ctx->rctx->cdb, name)) { + result = sbus_opath_compose(mem_ctx, PATH_BACKENDS, name); + if (result == NULL) { + return ENOMEM; + } + + *_path = result; + return EOK; + } + + DEBUG(SSSDBG_MINOR_FAILURE, "Backend \"%s\" does not exist", name); + return ENOENT; +} + +errno_t +ifp_component_get_name(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char **_out) +{ + char *name; + errno_t ret; + + ret = check_and_get_component_from_path(mem_ctx, ctx->rctx->cdb, + sbus_req->path, NULL, &name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unknown object [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + *_out = name; + + return EOK; +} + +errno_t +ifp_component_get_debug_level(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + uint32_t *_out) +{ + TALLOC_CTX *tmp_ctx; + const char *confdb_path = NULL; + enum component_type type; + char *name; + int level; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + ret = check_and_get_component_from_path(tmp_ctx, ctx->rctx->cdb, + sbus_req->path, &type, &name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unknown object [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + switch (type) { + case COMPONENT_MONITOR: + confdb_path = CONFDB_MONITOR_CONF_ENTRY; + break; + case COMPONENT_RESPONDER: + confdb_path = talloc_asprintf(tmp_ctx, CONFDB_SERVICE_PATH_TMPL, name); + break; + case COMPONENT_BACKEND: + confdb_path = talloc_asprintf(tmp_ctx, CONFDB_DOMAIN_PATH_TMPL, name); + break; + } + + if (confdb_path == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory\n"); + ret = ENOMEM; + goto done; + } + + ret = confdb_get_int(ctx->rctx->cdb, confdb_path, + CONFDB_SERVICE_DEBUG_LEVEL, SSSDBG_DEFAULT, &level); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to retrieve configuration option" + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + *_out = level; + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +ifp_component_get_enabled(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + bool *_out) +{ + TALLOC_CTX *tmp_ctx; + enum component_type type; + const char *param = NULL; + char **values; + char *name; + errno_t ret; + int i; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + ret = check_and_get_component_from_path(tmp_ctx, ctx->rctx->cdb, + sbus_req->path, &type, &name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unknown object [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + switch (type) { + case COMPONENT_MONITOR: + *_out = true; + ret = EOK; + goto done; + case COMPONENT_RESPONDER: + param = CONFDB_MONITOR_ACTIVE_SERVICES; + break; + case COMPONENT_BACKEND: + param = CONFDB_MONITOR_ACTIVE_DOMAINS; + break; + } + + ret = confdb_get_string_as_list(ctx->rctx->cdb, tmp_ctx, + CONFDB_MONITOR_CONF_ENTRY, param, &values); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to retrieve configuration option" + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + for (i = 0; values[i] != NULL; i++) { + if (strcmp(values[i], name) == 0) { + *_out = true; + break; + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +ifp_component_get_type(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char **_out) +{ + enum component_type type; + errno_t ret; + + ret = check_and_get_component_from_path(mem_ctx, ctx->rctx->cdb, + sbus_req->path, &type, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unknown object [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + switch (type) { + case COMPONENT_MONITOR: + *_out = "monitor"; + break; + case COMPONENT_RESPONDER: + *_out = "responder"; + break; + case COMPONENT_BACKEND: + *_out = "backend"; + break; + } + + return EOK; +} + +errno_t +ifp_backend_get_providers(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char ***_out) +{ + TALLOC_CTX *tmp_ctx; + const char *confdb_path; + char *name; + enum component_type type; + const char **out; + char *value; + static const char *providers[] = {CONFDB_DOMAIN_ID_PROVIDER, + CONFDB_DOMAIN_AUTH_PROVIDER, + CONFDB_DOMAIN_ACCESS_PROVIDER, + CONFDB_DOMAIN_CHPASS_PROVIDER, + CONFDB_DOMAIN_SUDO_PROVIDER, + CONFDB_DOMAIN_AUTOFS_PROVIDER, + CONFDB_DOMAIN_SELINUX_PROVIDER, + CONFDB_DOMAIN_HOSTID_PROVIDER, + CONFDB_DOMAIN_SUBDOMAINS_PROVIDER, + CONFDB_DOMAIN_SESSION_PROVIDER}; + int num_providers = sizeof(providers) / sizeof(providers[0]); + errno_t ret; + int i; + int j; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = check_and_get_component_from_path(tmp_ctx, ctx->rctx->cdb, + sbus_req->path, &type, &name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unknown object [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + if (type != COMPONENT_BACKEND) { + ret = EINVAL; + goto done; + } + + confdb_path = talloc_asprintf(tmp_ctx, CONFDB_DOMAIN_PATH_TMPL, name); + if (confdb_path == NULL) { + ret = ENOMEM; + goto done; + } + + out = talloc_zero_array(tmp_ctx, const char *, num_providers + 1); + if (out == NULL) { + ret = ENOMEM; + goto done; + } + + j = 0; + for (i = 0; i < num_providers; i++) { + ret = confdb_get_string(ctx->rctx->cdb, tmp_ctx, confdb_path, + providers[i], NULL, &value); + if (ret != EOK) { + goto done; + } + + if (value == NULL) { + continue; + } + + out[j] = talloc_asprintf(out, "%s=%s", providers[i], value); + if (out[j] == NULL) { + ret = ENOMEM; + goto done; + } + + j++; + } + + *_out = talloc_steal(mem_ctx, out); + +done: + talloc_free(tmp_ctx); + + return ret; +} diff --git a/src/responder/ifp/ifp_components.h b/src/responder/ifp/ifp_components.h new file mode 100644 index 0000000..50e8c44 --- /dev/null +++ b/src/responder/ifp/ifp_components.h @@ -0,0 +1,100 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2014 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/>. +*/ + +#ifndef _IFP_COMPONENTS_H_ +#define _IFP_COMPONENTS_H_ + +#include "responder/ifp/ifp_iface/ifp_iface_async.h" +#include "responder/ifp/ifp_private.h" + +/* org.freedesktop.sssd.infopipe */ + +errno_t +ifp_list_components(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char ***_paths); + +errno_t +ifp_list_responders(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char ***_paths); +errno_t +ifp_list_backends(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char ***_paths); + +errno_t +ifp_find_monitor(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char **_path); + +errno_t +ifp_find_responder_by_name(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char *name, + const char **_path); + +errno_t +ifp_find_backend_by_name(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char *name, + const char **_path); + +/* org.freedesktop.sssd.infopipe.Components */ + +errno_t +ifp_component_get_name(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char **_out); + +errno_t +ifp_component_get_debug_level(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + uint32_t *_out); + +errno_t +ifp_component_get_enabled(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + bool *_out); + +errno_t +ifp_component_get_type(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char **_out); + +/* org.freedesktop.sssd.infopipe.Components.Backends */ + +errno_t +ifp_backend_get_providers(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char ***_out); + +#endif /* _IFP_COMPONENTS_H_ */ diff --git a/src/responder/ifp/ifp_domains.c b/src/responder/ifp/ifp_domains.c new file mode 100644 index 0000000..16d80e6 --- /dev/null +++ b/src/responder/ifp/ifp_domains.c @@ -0,0 +1,1022 @@ +/* + Authors: + Jakub Hrozek <jhrozek@redhat.com> + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2014 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 <talloc.h> +#include <tevent.h> +#include <string.h> + +#include "db/sysdb.h" +#include "util/util.h" +#include "confdb/confdb.h" +#include "responder/common/responder.h" +#include "responder/ifp/ifp_domains.h" +#include "responder/ifp/ifp_iface/ifp_iface_async.h" +#include "sss_iface/sss_iface_async.h" + +#define RETURN_DOM_PROP(dbus_req, ctx, out, property) ({ \ + struct sss_domain_info *__dom; \ + errno_t __ret; \ + \ + __dom = get_domain_info_from_req((dbus_req), (ctx)); \ + if (__dom == NULL) { \ + __ret = ERR_DOMAIN_NOT_FOUND; \ + } else { \ + *(out) = __dom->property; \ + __ret = EOK; \ + } \ + __ret; \ +}) + + +struct ifp_list_domains_state { + struct ifp_ctx *ifp_ctx; + const char **paths; +}; + +static void ifp_list_domains_done(struct tevent_req *subreq); + +struct tevent_req * +ifp_list_domains_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ifp_ctx) +{ + struct ifp_list_domains_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ifp_list_domains_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + state->ifp_ctx = ifp_ctx; + + subreq = sss_dp_get_domains_send(state, ifp_ctx->rctx, false, NULL); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ifp_list_domains_done, req); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void ifp_list_domains_done(struct tevent_req *subreq) +{ + struct ifp_list_domains_state *state; + struct sss_domain_info *dom; + struct tevent_req *req; + size_t num_domains; + size_t pi; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ifp_list_domains_state); + + ret = sss_dp_get_domains_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to refresh domain objects\n"); + tevent_req_error(req, ret); + return; + } + + ret = sysdb_master_domain_update(state->ifp_ctx->rctx->domains); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to refresh subdomain list\n"); + tevent_req_error(req, ret); + return; + } + + num_domains = 0; + for (dom = state->ifp_ctx->rctx->domains; + dom != NULL; + dom = get_next_domain(dom, SSS_GND_DESCEND)) { + num_domains++; + } + + state->paths = talloc_zero_array(state, const char *, num_domains + 1); + if (state->paths == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + pi = 0; + for (dom = state->ifp_ctx->rctx->domains; + dom != NULL; + dom = get_next_domain(dom, SSS_GND_DESCEND)) { + state->paths[pi] = sbus_opath_compose(state->paths, IFP_PATH_DOMAINS, dom->name); + if (state->paths[pi] == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not create path for dom %s\n", + dom->name); + tevent_req_error(req, ENOMEM); + return; + } + pi++; + } + + tevent_req_done(req); + return; +} + +errno_t ifp_list_domains_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char ***_paths) +{ + struct ifp_list_domains_state *state; + state = tevent_req_data(req, struct ifp_list_domains_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_paths = talloc_steal(mem_ctx, state->paths); + + return EOK; +} + +struct ifp_find_domain_by_name_state { + struct ifp_ctx *ifp_ctx; + const char *name; + const char *path; +}; + +static void ifp_find_domain_by_name_done(struct tevent_req *subreq); + +struct tevent_req * +ifp_find_domain_by_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ifp_ctx, + const char *name) +{ + struct ifp_find_domain_by_name_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ifp_find_domain_by_name_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + state->ifp_ctx = ifp_ctx; + state->name = name; + + subreq = sss_dp_get_domains_send(state, ifp_ctx->rctx, false, NULL); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ifp_find_domain_by_name_done, req); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void ifp_find_domain_by_name_done(struct tevent_req *subreq) +{ + struct ifp_find_domain_by_name_state *state; + struct sss_domain_info *iter; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ifp_find_domain_by_name_state); + + ret = sss_dp_get_domains_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to refresh domain objects\n"); + tevent_req_error(req, ret); + return; + } + + ret = sysdb_master_domain_update(state->ifp_ctx->rctx->domains); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to refresh subdomain list\n"); + tevent_req_error(req, ret); + return; + } + + /* Reply with the domain that was asked for */ + for (iter = state->ifp_ctx->rctx->domains; + iter != NULL; + iter = get_next_domain(iter, SSS_GND_DESCEND)) { + if (strcasecmp(iter->name, state->name) == 0) { + break; + } + } + + if (iter == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "Domain not found: %s\n", state->name); + tevent_req_error(req, ERR_DOMAIN_NOT_FOUND); + return; + } + + state->path = sbus_opath_compose(state, IFP_PATH_DOMAINS, iter->name); + if (state->path == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not create path for domain %s, skipping\n", iter->name); + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_done(req); + return; +} + +errno_t +ifp_find_domain_by_name_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char **_path) +{ + struct ifp_find_domain_by_name_state *state; + state = tevent_req_data(req, struct ifp_find_domain_by_name_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_path = talloc_steal(mem_ctx, state->path); + + return EOK; +} + +static struct sss_domain_info * +get_domain_info_from_req(struct sbus_request *dbus_req, + struct ifp_ctx *ctx) +{ + struct sss_domain_info *domains = NULL; + struct sss_domain_info *iter = NULL; + char *name = NULL; + + name = sbus_opath_object_name(NULL, dbus_req->path, IFP_PATH_DOMAINS); + if (name == NULL) { + return NULL; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Looking for domain %s\n", name); + + domains = ctx->rctx->domains; + for (iter = domains; iter != NULL; + iter = get_next_domain(iter, SSS_GND_DESCEND)) { + if (strcasecmp(iter->name, name) == 0) { + break; + } + } + + talloc_free(name); + return iter; +} + +static errno_t +get_server_list(TALLOC_CTX *mem_ctx, + struct sbus_request *dbus_req, + struct ifp_ctx *ctx, + const char ***_out, + bool backup) +{ + TALLOC_CTX *tmp_ctx; + static const char *srv[] = {"_srv_", NULL}; + struct sss_domain_info *dom = NULL; + char *conf_path = NULL; + const char *option = NULL; + const char **out = NULL; + char **servers = NULL; + int num_servers; + errno_t ret; + int i; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + dom = get_domain_info_from_req(dbus_req, ctx); + if (dom == NULL) { + return ERR_DOMAIN_NOT_FOUND; + } + + if (dom->parent != NULL) { + /* subdomains are not present in configuration */ + ret = ENOENT; + goto done; + } + + conf_path = talloc_asprintf(tmp_ctx, CONFDB_DOMAIN_PATH_TMPL, dom->name); + if (conf_path == NULL) { + ret = ENOMEM; + goto done; + } + + /* TODO: replace hardcoded values with option names from the provider */ + if (strcasecmp(dom->provider, "ldap") == 0) { + option = backup == false ? "ldap_uri" : "ldap_backup_uri"; + } else if (strcasecmp(dom->provider, "ipa") == 0) { + option = backup == false ? "ipa_server" : "ipa_backup_server"; + } else if (strcasecmp(dom->provider, "ad") == 0) { + option = backup == false ? "ad_server" : "ad_backup_server"; + } else { + ret = EINVAL; + goto done; + } + + ret = confdb_get_string_as_list(ctx->rctx->cdb, tmp_ctx, conf_path, + option, &servers); + if (ret != EOK) { + goto done; + } + + for (num_servers = 0; servers[num_servers] != NULL; num_servers++); + + if (num_servers == 0) { + ret = ENOENT; + goto done; + } + + out = talloc_zero_array(mem_ctx, const char *, num_servers + 1); + if (out == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = 0; i < num_servers; i++) { + out[i] = talloc_steal(out, servers[i]); + } + + *_out = out; + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + if (ret == ENOENT) { + *_out = srv; + } + + return ret; +} + +errno_t +ifp_dom_get_name(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char **_out) +{ + return RETURN_DOM_PROP(sbus_req, ctx, _out, name); +} + +errno_t +ifp_dom_get_provider(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char **_out) +{ + return RETURN_DOM_PROP(sbus_req, ctx, _out, provider); +} + +errno_t +ifp_dom_get_primary_servers(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char ***_out) +{ + return get_server_list(mem_ctx, sbus_req, ctx, _out, false); +} + +errno_t +ifp_dom_get_backup_servers(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char ***_out) +{ + return get_server_list(mem_ctx, sbus_req, ctx, _out, true); +} + +errno_t +ifp_dom_get_min_id(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + uint32_t *_out) +{ + return RETURN_DOM_PROP(sbus_req, ctx, _out, id_min); +} + +errno_t +ifp_dom_get_max_id(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + uint32_t *_out) +{ + return RETURN_DOM_PROP(sbus_req, ctx, _out, id_max); +} + +errno_t +ifp_dom_get_realm(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char **_out) +{ + return RETURN_DOM_PROP(sbus_req, ctx, _out, realm); +} + +errno_t +ifp_dom_get_forest(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char **_out) +{ + return RETURN_DOM_PROP(sbus_req, ctx, _out, forest); +} + +errno_t +ifp_dom_get_login_format(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char **_out) +{ + return RETURN_DOM_PROP(sbus_req, ctx, _out, names->re_pattern); +} + +errno_t +ifp_dom_get_fqdn_format(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char **_out) +{ + return RETURN_DOM_PROP(sbus_req, ctx, _out, names->fq_fmt); +} + +errno_t +ifp_dom_get_enumerable(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + bool *_out) +{ + return RETURN_DOM_PROP(sbus_req, ctx, _out, enumerate); +} + +errno_t +ifp_dom_get_use_fqdn(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + bool *_out) +{ + return RETURN_DOM_PROP(sbus_req, ctx, _out, fqnames); +} + +errno_t +ifp_dom_get_subdomain(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + bool *_out) +{ + struct sss_domain_info *dom; + + dom = get_domain_info_from_req(sbus_req, ctx); + if (dom == NULL) { + return ERR_DOMAIN_NOT_FOUND; + } + + *_out = dom->parent != NULL ? true : false; + + return EOK; +} + +errno_t +ifp_dom_get_parent_domain(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char **_out) +{ + struct sss_domain_info *dom; + const char *path; + + dom = get_domain_info_from_req(sbus_req, ctx); + if (dom == NULL) { + return ERR_DOMAIN_NOT_FOUND; + } + + if (dom->parent == NULL) { + *_out = "/"; + return EOK; + } + + path = sbus_opath_compose(mem_ctx, IFP_PATH_DOMAINS, dom->parent->name); + if (path == NULL) { + return ENOMEM; + } + + *_out = path; + + return EOK; +} + +struct ifp_domains_domain_is_online_state { + bool is_online; +}; + +static void ifp_domains_domain_is_online_done(struct tevent_req *subreq); + +struct tevent_req * +ifp_domains_domain_is_online_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ifp_ctx) +{ + struct ifp_domains_domain_is_online_state *state; + struct sss_domain_info *dom; + struct tevent_req *subreq; + struct tevent_req *req; + struct be_conn *be_conn; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ifp_domains_domain_is_online_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + dom = get_domain_info_from_req(sbus_req, ifp_ctx); + if (dom == NULL) { + ret = ERR_DOMAIN_NOT_FOUND; + goto done; + } + + ret = sss_dp_get_domain_conn(ifp_ctx->rctx, dom->conn_name, &be_conn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "BUG: The Data Provider connection for " + "%s is not available!\n", dom->name); + goto done; + } + + subreq = sbus_call_dp_backend_IsOnline_send(state, be_conn->conn, + be_conn->bus_name, SSS_BUS_PATH, dom->name); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ifp_domains_domain_is_online_done, req); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void ifp_domains_domain_is_online_done(struct tevent_req *subreq) +{ + struct ifp_domains_domain_is_online_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ifp_domains_domain_is_online_state); + + ret = sbus_call_dp_backend_IsOnline_recv(subreq, &state->is_online); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +errno_t +ifp_domains_domain_is_online_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + bool *_is_online) +{ + struct ifp_domains_domain_is_online_state *state; + state = tevent_req_data(req, struct ifp_domains_domain_is_online_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_is_online = state->is_online; + + return EOK; +} + +struct ifp_domains_domain_list_services_state { + const char **services; +}; + +static void ifp_domains_domain_list_services_done(struct tevent_req *subreq); + +struct tevent_req * +ifp_domains_domain_list_services_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ifp_ctx) +{ + struct ifp_domains_domain_list_services_state *state; + struct sss_domain_info *dom; + struct tevent_req *subreq; + struct tevent_req *req; + struct be_conn *be_conn; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ifp_domains_domain_list_services_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + dom = get_domain_info_from_req(sbus_req, ifp_ctx); + if (dom == NULL) { + ret = ERR_DOMAIN_NOT_FOUND; + goto done; + } + + ret = sss_dp_get_domain_conn(ifp_ctx->rctx, dom->conn_name, &be_conn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "BUG: The Data Provider connection for " + "%s is not available!\n", dom->name); + goto done; + } + + subreq = sbus_call_dp_failover_ListServices_send(state, be_conn->conn, + be_conn->bus_name, SSS_BUS_PATH, dom->name); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ifp_domains_domain_list_services_done, req); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void ifp_domains_domain_list_services_done(struct tevent_req *subreq) +{ + struct ifp_domains_domain_list_services_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ifp_domains_domain_list_services_state); + + ret = sbus_call_dp_failover_ListServices_recv(state, subreq, &state->services); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +errno_t +ifp_domains_domain_list_services_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char ***_services) +{ + struct ifp_domains_domain_list_services_state *state; + state = tevent_req_data(req, struct ifp_domains_domain_list_services_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_services = talloc_steal(mem_ctx, state->services); + + return EOK; +} + +struct ifp_domains_domain_active_server_state { + const char *server; +}; + +static void ifp_domains_domain_active_server_done(struct tevent_req *subreq); + +struct tevent_req * +ifp_domains_domain_active_server_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ifp_ctx, + const char *service) +{ + struct ifp_domains_domain_active_server_state *state; + struct sss_domain_info *dom; + struct tevent_req *subreq; + struct tevent_req *req; + struct be_conn *be_conn; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ifp_domains_domain_active_server_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + dom = get_domain_info_from_req(sbus_req, ifp_ctx); + if (dom == NULL) { + ret = ERR_DOMAIN_NOT_FOUND; + goto done; + } + + ret = sss_dp_get_domain_conn(ifp_ctx->rctx, dom->conn_name, &be_conn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "BUG: The Data Provider connection for " + "%s is not available!\n", dom->name); + goto done; + } + + subreq = sbus_call_dp_failover_ActiveServer_send(state, be_conn->conn, + be_conn->bus_name, SSS_BUS_PATH, service); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ifp_domains_domain_active_server_done, req); + + ret = EAGAIN; + +done: +if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); +} + + return req; +} + +static void ifp_domains_domain_active_server_done(struct tevent_req *subreq) +{ + struct ifp_domains_domain_active_server_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ifp_domains_domain_active_server_state); + + ret = sbus_call_dp_failover_ActiveServer_recv(state, subreq, &state->server); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +errno_t +ifp_domains_domain_active_server_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char **_server) +{ + struct ifp_domains_domain_active_server_state *state; + state = tevent_req_data(req, struct ifp_domains_domain_active_server_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_server = talloc_steal(mem_ctx, state->server); + + return EOK; +} + +struct ifp_domains_domain_list_servers_state { + const char **servers; +}; + +static void ifp_domains_domain_list_servers_done(struct tevent_req *subreq); + +struct tevent_req * +ifp_domains_domain_list_servers_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ifp_ctx, + const char *service) +{ + struct ifp_domains_domain_list_servers_state *state; + struct sss_domain_info *dom; + struct tevent_req *subreq; + struct tevent_req *req; + struct be_conn *be_conn; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ifp_domains_domain_list_servers_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + dom = get_domain_info_from_req(sbus_req, ifp_ctx); + if (dom == NULL) { + ret = ERR_DOMAIN_NOT_FOUND; + goto done; + } + + ret = sss_dp_get_domain_conn(ifp_ctx->rctx, dom->conn_name, &be_conn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "BUG: The Data Provider connection for " + "%s is not available!\n", dom->name); + goto done; + } + + subreq = sbus_call_dp_failover_ListServers_send(state, be_conn->conn, + be_conn->bus_name, SSS_BUS_PATH, service); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ifp_domains_domain_list_servers_done, req); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void ifp_domains_domain_list_servers_done(struct tevent_req *subreq) +{ + struct ifp_domains_domain_list_servers_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ifp_domains_domain_list_servers_state); + + ret = sbus_call_dp_failover_ListServers_recv(state, subreq, &state->servers); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +errno_t +ifp_domains_domain_list_servers_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char ***_servers) +{ + struct ifp_domains_domain_list_servers_state *state; + state = tevent_req_data(req, struct ifp_domains_domain_list_servers_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_servers = talloc_steal(mem_ctx, state->servers); + + return EOK; +} + +struct ifp_domains_domain_refresh_access_rules_state { + int dummy; +}; + +static void ifp_domains_domain_refresh_access_rules_done(struct tevent_req *subreq); + +struct tevent_req * +ifp_domains_domain_refresh_access_rules_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ifp_ctx) +{ + struct ifp_domains_domain_refresh_access_rules_state *state; + struct sss_domain_info *dom; + struct tevent_req *subreq; + struct tevent_req *req; + struct be_conn *be_conn; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ifp_domains_domain_refresh_access_rules_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + dom = get_domain_info_from_req(sbus_req, ifp_ctx); + if (dom == NULL) { + ret = ERR_DOMAIN_NOT_FOUND; + goto done; + } + + ret = sss_dp_get_domain_conn(ifp_ctx->rctx, dom->conn_name, &be_conn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "BUG: The Data Provider connection for " + "%s is not available!\n", dom->name); + goto done; + } + + subreq = sbus_call_dp_access_RefreshRules_send(state, be_conn->conn, + be_conn->bus_name, SSS_BUS_PATH); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ifp_domains_domain_refresh_access_rules_done, req); + + ret = EAGAIN; + +done: +if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); +} + + return req; +} + +static void ifp_domains_domain_refresh_access_rules_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + ret = sbus_call_dp_access_RefreshRules_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +errno_t +ifp_domains_domain_refresh_access_rules_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} diff --git a/src/responder/ifp/ifp_domains.h b/src/responder/ifp/ifp_domains.h new file mode 100644 index 0000000..1c84163 --- /dev/null +++ b/src/responder/ifp/ifp_domains.h @@ -0,0 +1,193 @@ +/* + Authors: + Jakub Hrozek <jhrozek@redhat.com> + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2014 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/>. +*/ + +#ifndef IFP_DOMAINS_H_ +#define IFP_DOMAINS_H_ + +#include "responder/ifp/ifp_private.h" + +/* org.freedesktop.sssd.infopipe */ + +struct tevent_req * +ifp_list_domains_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ifp_ctx); + +errno_t ifp_list_domains_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char ***_paths); + +struct tevent_req * +ifp_find_domain_by_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ifp_ctx, + const char *name); + +errno_t +ifp_find_domain_by_name_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char **_path); + +/* org.freedesktop.sssd.infopipe.Domains */ + +errno_t +ifp_dom_get_name(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char **_out); + +errno_t +ifp_dom_get_provider(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char **_out); + +errno_t +ifp_dom_get_primary_servers(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char ***_out); + +errno_t +ifp_dom_get_backup_servers(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char ***_out); + +errno_t +ifp_dom_get_min_id(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + uint32_t *_out); + +errno_t +ifp_dom_get_max_id(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + uint32_t *_out); + +errno_t +ifp_dom_get_realm(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char **_out); + +errno_t +ifp_dom_get_forest(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char **_out); + +errno_t +ifp_dom_get_login_format(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char **_out); + +errno_t +ifp_dom_get_fqdn_format(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char **_out); + +errno_t +ifp_dom_get_enumerable(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + bool *_out); + +errno_t +ifp_dom_get_use_fqdn(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + bool *_out); + +errno_t +ifp_dom_get_subdomain(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + bool *_out); + +errno_t +ifp_dom_get_parent_domain(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char **_out); + +struct tevent_req * +ifp_domains_domain_is_online_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ifp_ctx); + +errno_t +ifp_domains_domain_is_online_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + bool *_is_online); + +struct tevent_req * +ifp_domains_domain_list_services_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ifp_ctx); + +errno_t +ifp_domains_domain_list_services_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char ***_services); + +struct tevent_req * +ifp_domains_domain_active_server_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ifp_ctx, + const char *service); + +errno_t +ifp_domains_domain_active_server_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char **_server); + +struct tevent_req * +ifp_domains_domain_list_servers_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ifp_ctx, + const char *service); + +errno_t +ifp_domains_domain_list_servers_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char ***_servers); + +struct tevent_req * +ifp_domains_domain_refresh_access_rules_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ifp_ctx); + +errno_t +ifp_domains_domain_refresh_access_rules_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req); + +#endif /* IFP_DOMAINS_H_ */ diff --git a/src/responder/ifp/ifp_groups.c b/src/responder/ifp/ifp_groups.c new file mode 100644 index 0000000..e65716d --- /dev/null +++ b/src/responder/ifp/ifp_groups.c @@ -0,0 +1,1169 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2015 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 <talloc.h> +#include <tevent.h> + +#include "util/util.h" +#include "db/sysdb.h" +#include "util/strtonum.h" +#include "responder/common/responder.h" +#include "responder/common/cache_req/cache_req.h" +#include "responder/ifp/ifp_groups.h" +#include "responder/ifp/ifp_users.h" +#include "responder/ifp/ifp_cache.h" +#include "responder/ifp/ifp_iface/ifp_iface_async.h" + +char * ifp_groups_build_path_from_msg(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + struct ldb_message *msg) +{ + const char *key = NULL; + + switch (domain->type) { + case DOM_TYPE_APPLICATION: + key = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); + break; + case DOM_TYPE_POSIX: + key = ldb_msg_find_attr_as_string(msg, SYSDB_GIDNUM, NULL); + break; + } + + + if (key == NULL) { + return NULL; + } + + return sbus_opath_compose(mem_ctx, IFP_PATH_GROUPS, domain->name, key); +} + +static errno_t ifp_groups_decompose_path(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domains, + const char *path, + struct sss_domain_info **_domain, + char **_key) +{ + char **parts = NULL; + struct sss_domain_info *domain; + errno_t ret; + + ret = sbus_opath_decompose_expected(NULL, path, IFP_PATH_GROUPS, 2, &parts); + if (ret != EOK) { + return ret; + } + + domain = find_domain_by_name(domains, parts[0], false); + if (domain == NULL) { + ret = ERR_DOMAIN_NOT_FOUND; + goto done; + } + + *_domain = domain; + *_key = talloc_steal(mem_ctx, parts[1]); + +done: + talloc_free(parts); + return ret; +} + +static int ifp_groups_list_copy(struct ifp_list_ctx *list_ctx, + struct sss_domain_info *domain, + struct ldb_result *result) +{ + size_t copy_count, i; + errno_t ret; + + ret = ifp_list_ctx_remaining_capacity(list_ctx, result->count, ©_count); + if (ret != EOK) { + goto done; + } + + for (i = 0; i < copy_count; i++) { + list_ctx->paths[list_ctx->path_count + i] = \ + ifp_groups_build_path_from_msg(list_ctx->paths, domain, + result->msgs[i]); + if (list_ctx->paths[list_ctx->path_count + i] == NULL) { + ret = ENOMEM; + goto done; + } + } + + list_ctx->path_count += copy_count; + ret = EOK; + +done: + return ret; +} + +struct ifp_groups_find_by_name_state { + const char *path; +}; + +static void ifp_groups_find_by_name_done(struct tevent_req *subreq); + +struct tevent_req * +ifp_groups_find_by_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char *name) +{ + struct ifp_groups_find_by_name_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ifp_groups_find_by_name_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + subreq = cache_req_group_by_name_send(state, ctx->rctx->ev, ctx->rctx, + ctx->rctx->ncache, 0, + CACHE_REQ_ANY_DOM, NULL, + name); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ifp_groups_find_by_name_done, req); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void ifp_groups_find_by_name_done(struct tevent_req *subreq) +{ + struct ifp_groups_find_by_name_state *state; + struct cache_req_result *result; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ifp_groups_find_by_name_state); + + ret = cache_req_group_by_name_recv(state, subreq, &result); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to find group [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + state->path = ifp_groups_build_path_from_msg(state, result->domain, + result->msgs[0]); + if (state->path == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_done(req); + return; +} + +errno_t +ifp_groups_find_by_name_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char **_path) +{ + struct ifp_groups_find_by_name_state *state; + state = tevent_req_data(req, struct ifp_groups_find_by_name_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_path = talloc_steal(mem_ctx, state->path); + + return EOK; +} + +struct ifp_groups_find_by_id_state { + const char *path; +}; + +static void ifp_groups_find_by_id_done(struct tevent_req *subreq); + +struct tevent_req * +ifp_groups_find_by_id_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + uint32_t id) +{ + struct ifp_groups_find_by_id_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ifp_groups_find_by_id_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + subreq = cache_req_group_by_id_send(state, ctx->rctx->ev, ctx->rctx, + ctx->rctx->ncache, 0, NULL, id); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ifp_groups_find_by_id_done, req); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void ifp_groups_find_by_id_done(struct tevent_req *subreq) +{ + struct ifp_groups_find_by_id_state *state; + struct cache_req_result *result; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ifp_groups_find_by_id_state); + + ret = cache_req_group_by_id_recv(state, subreq, &result); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to find group [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + state->path = ifp_groups_build_path_from_msg(state, result->domain, + result->msgs[0]); + if (state->path == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_done(req); + return; +} + +errno_t +ifp_groups_find_by_id_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char **_path) +{ + struct ifp_groups_find_by_id_state *state; + state = tevent_req_data(req, struct ifp_groups_find_by_id_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_path = talloc_steal(mem_ctx, state->path); + + return EOK; +} + +struct ifp_groups_list_by_name_state { + struct ifp_ctx *ifp_ctx; + struct ifp_list_ctx *list_ctx; +}; + +static errno_t ifp_groups_list_by_name_step(struct tevent_req *req); +static void ifp_groups_list_by_name_done(struct tevent_req *subreq); + +struct tevent_req * +ifp_groups_list_by_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char *filter, + uint32_t limit) +{ + struct ifp_groups_list_by_name_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ifp_groups_list_by_name_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + state->ifp_ctx = ctx; + state->list_ctx = ifp_list_ctx_new(state, ctx, NULL, filter, limit); + if (state->list_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ret = ifp_groups_list_by_name_step(req); + +done: + if (ret == EOK) { + tevent_req_done(req); + tevent_req_post(req, ev); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static errno_t +ifp_groups_list_by_name_step(struct tevent_req *req) +{ + struct ifp_groups_list_by_name_state *state; + struct tevent_req *subreq; + + state = tevent_req_data(req, struct ifp_groups_list_by_name_state); + + if (state->list_ctx->dom == NULL) { + return EOK; + } + + subreq = cache_req_group_by_filter_send(state->list_ctx, + state->ifp_ctx->rctx->ev, + state->ifp_ctx->rctx, + CACHE_REQ_ANY_DOM, + state->list_ctx->dom->name, + state->list_ctx->filter); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + return ENOMEM; + } + + tevent_req_set_callback(subreq, ifp_groups_list_by_name_done, req); + + state->list_ctx->dom = get_next_domain(state->list_ctx->dom, + SSS_GND_DESCEND); + + return EAGAIN; +} + +static void ifp_groups_list_by_name_done(struct tevent_req *subreq) +{ + struct ifp_groups_list_by_name_state *state; + struct cache_req_result *result; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ifp_groups_list_by_name_state); + + ret = cache_req_group_by_name_recv(state, subreq, &result); + talloc_zfree(subreq); + if (ret == EOK) { + ret = ifp_groups_list_copy(state->list_ctx, result->domain, + result->ldb_result); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to copy domain result\n"); + tevent_req_error(req, ret); + return; + } + } else if (ret != ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to list groups [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + ret = ifp_groups_list_by_name_step(req); + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } +} + +errno_t +ifp_groups_list_by_name_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char ***_paths) +{ + struct ifp_groups_list_by_name_state *state; + state = tevent_req_data(req, struct ifp_groups_list_by_name_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_paths = talloc_steal(mem_ctx, state->list_ctx->paths); + + return EOK; +} + +struct ifp_groups_list_by_domain_and_name_state { + struct ifp_list_ctx *list_ctx; +}; + +static void ifp_groups_list_by_domain_and_name_done(struct tevent_req *subreq); + +struct tevent_req * +ifp_groups_list_by_domain_and_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char *domain, + const char *filter, + uint32_t limit) +{ + struct ifp_groups_list_by_domain_and_name_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ifp_groups_list_by_domain_and_name_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + state->list_ctx = ifp_list_ctx_new(state, ctx, NULL, filter, limit); + if (state->list_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + subreq = cache_req_group_by_filter_send(state->list_ctx, ctx->rctx->ev, + ctx->rctx, CACHE_REQ_ANY_DOM, + domain, filter); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ifp_groups_list_by_domain_and_name_done, req); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void ifp_groups_list_by_domain_and_name_done(struct tevent_req *subreq) +{ + struct ifp_groups_list_by_domain_and_name_state *state; + struct cache_req_result *result; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ifp_groups_list_by_domain_and_name_state); + + ret = cache_req_group_by_filter_recv(state, subreq, &result); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + ret = ifp_groups_list_copy(state->list_ctx, result->domain, + result->ldb_result); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to copy domain result\n"); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +errno_t +ifp_groups_list_by_domain_and_name_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char ***_paths) +{ + struct ifp_groups_list_by_domain_and_name_state *state; + state = tevent_req_data(req, struct ifp_groups_list_by_domain_and_name_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_paths = talloc_steal(mem_ctx, state->list_ctx->paths); + + return EOK; +} + +static errno_t +ifp_groups_get_from_cache(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *key, + struct ldb_message **_group) +{ + struct ldb_result *group_res = NULL; + errno_t ret; + gid_t gid; + char *endptr; + + switch (domain->type) { + case DOM_TYPE_POSIX: + gid = strtouint32(key, &endptr, 10); + if ((errno != 0) || *endptr || (key == endptr)) { + ret = errno ? errno : EINVAL; + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid GID value\n"); + return ret; + } + + ret = sysdb_getgrgid_with_views(NULL, domain, gid, &group_res); + if (ret == EOK && group_res->count == 0) { + *_group = NULL; + ret = ENOENT; + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to lookup group %u@%s [%d]: %s\n", + gid, domain->name, ret, sss_strerror(ret)); + goto done; + } + break; + case DOM_TYPE_APPLICATION: + ret = sysdb_getgrnam_with_views(NULL, domain, key, &group_res); + if (ret == EOK && group_res->count == 0) { + *_group = NULL; + ret = ENOENT; + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to lookup group %s@%s [%d]: %s\n", + key, domain->name, ret, sss_strerror(ret)); + goto done; + } + break; + } + + if (group_res->count > 1) { + DEBUG(SSSDBG_CRIT_FAILURE, "More groups matched by the single key\n"); + return EIO; + } + + *_group = talloc_steal(mem_ctx, group_res->msgs[0]); + + ret = EOK; + +done: + talloc_free(group_res); + + return ret; +} + +static errno_t +ifp_groups_group_get(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + struct sss_domain_info **_domain, + struct ldb_message **_group) +{ + struct sss_domain_info *domain; + char *key; + errno_t ret; + + ret = ifp_groups_decompose_path(NULL, ctx->rctx->domains, sbus_req->path, + &domain, &key); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to decompose object path" + "[%s] [%d]: %s\n", sbus_req->path, ret, sss_strerror(ret)); + return ret; + } + + if (_group != NULL) { + ret = ifp_groups_get_from_cache(mem_ctx, domain, key, _group); + } + + talloc_free(key); + + if (ret == EOK || ret == ENOENT) { + if (_domain != NULL) { + *_domain = domain; + } + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to retrieve group from cache\n"); + } + + return ret; +} + +struct resolv_ghosts_state { + struct tevent_context *ev; + struct sbus_request *sbus_req; + struct ifp_ctx *ctx; + + struct sss_domain_info *domain; + const char **ghosts; + int index; +}; + +static void resolv_ghosts_group_done(struct tevent_req *subreq); +static errno_t resolv_ghosts_step(struct tevent_req *req); +static void resolv_ghosts_done(struct tevent_req *subreq); + +static struct tevent_req *resolv_ghosts_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx) +{ + struct resolv_ghosts_state *state; + struct sss_domain_info *domain; + struct tevent_req *req; + struct tevent_req *subreq; + struct ldb_message *group; + const char *name; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct resolv_ghosts_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->sbus_req = sbus_req; + state->ctx = ctx; + + ret = ifp_groups_group_get(state, sbus_req, ctx, &domain, &group); + if (ret != EOK) { + goto immediately; + } + + name = ldb_msg_find_attr_as_string(group, SYSDB_NAME, NULL); + if (name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Group name is empty!\n"); + ret = ERR_INTERNAL; + goto immediately; + } + + subreq = cache_req_group_by_name_send(state, ev, ctx->rctx, + ctx->rctx->ncache, 0, + CACHE_REQ_ANY_DOM, + domain->name, + name); + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, resolv_ghosts_group_done, req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static void resolv_ghosts_group_done(struct tevent_req *subreq) +{ + struct resolv_ghosts_state *state; + struct ldb_message *group = NULL; + struct ldb_message_element *el; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct resolv_ghosts_state); + + ret = ifp_groups_group_get(state, state->sbus_req, state->ctx, + &state->domain, &group); + if (ret != EOK) { + goto done; + } + + el = ldb_msg_find_element(group, SYSDB_GHOST); + if (el == NULL || el->num_values == 0) { + ret = EOK; + goto done; + } + + state->ghosts = sss_ldb_el_to_string_list(state, el); + if (state->ghosts == NULL) { + ret = ENOMEM; + goto done; + } + + state->index = 0; + ret = resolv_ghosts_step(req); + +done: + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } +} + +errno_t resolv_ghosts_step(struct tevent_req *req) +{ + struct resolv_ghosts_state *state; + struct tevent_req *subreq; + + state = tevent_req_data(req, struct resolv_ghosts_state); + + if (state->ghosts[state->index] == NULL) { + return EOK; + } + + subreq = cache_req_user_by_name_send(state, state->ev, state->ctx->rctx, + state->ctx->rctx->ncache, 0, + CACHE_REQ_ANY_DOM, + state->domain->name, + state->ghosts[state->index]); + if (subreq == NULL) { + return ENOMEM; + } + + tevent_req_set_callback(subreq, resolv_ghosts_done, req); + + state->index++; + + return EAGAIN; +} + +static void resolv_ghosts_done(struct tevent_req *subreq) +{ + struct resolv_ghosts_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct resolv_ghosts_state); + + ret = cache_req_user_by_name_recv(state, subreq, NULL); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + ret = resolv_ghosts_step(req); + +done: + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } +} + +static errno_t resolv_ghosts_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct ifp_groups_group_update_member_list_state { + int dummy; +}; + +static void ifp_groups_group_update_member_list_done(struct tevent_req *subreq); + +struct tevent_req * +ifp_groups_group_update_member_list_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx) +{ + struct ifp_groups_group_update_member_list_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ifp_groups_group_update_member_list_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + subreq = resolv_ghosts_send(state, ev, sbus_req, ctx); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ifp_groups_group_update_member_list_done, req); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void ifp_groups_group_update_member_list_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + + ret = resolv_ghosts_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to resolve ghost members [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +errno_t +ifp_groups_group_update_member_list_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +errno_t +ifp_groups_group_get_name(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char **_out) +{ + struct sss_domain_info *domain; + struct ldb_message *msg; + const char *in_name; + const char *out; + errno_t ret; + + ret = ifp_groups_group_get(mem_ctx, sbus_req, ctx, &domain, &msg); + if (ret != EOK) { + return ret; + } + + in_name = sss_view_ldb_msg_find_attr_as_string(domain, msg, + SYSDB_NAME, NULL); + if (in_name == NULL) { + talloc_zfree(msg); + DEBUG(SSSDBG_OP_FAILURE, "No name?\n"); + return ERR_INTERNAL; + } + + out = ifp_format_name_attr(mem_ctx, ctx, in_name, domain); + talloc_zfree(msg); + if (out == NULL) { + return ENOMEM; + } + + *_out = out; + + return EOK; +} + +errno_t +ifp_groups_group_get_gid_number(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + uint32_t *_out) +{ + struct ldb_message *msg; + struct sss_domain_info *domain; + errno_t ret; + + ret = ifp_groups_group_get(mem_ctx, sbus_req, ctx, &domain, &msg); + if (ret != EOK) { + return ret; + } + + *_out = sss_view_ldb_msg_find_attr_as_uint64(domain, msg, SYSDB_GIDNUM, 0); + talloc_zfree(msg); + + return EOK; +} + +errno_t +ifp_groups_group_get_unique_id(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char **_out) +{ + struct ldb_message *msg; + struct sss_domain_info *domain; + const char *uuid; + errno_t ret; + + ret = ifp_groups_group_get(mem_ctx, sbus_req, ctx, &domain, &msg); + if (ret != EOK) { + return ret; + } + + uuid = sss_view_ldb_msg_find_attr_as_string(domain, msg, SYSDB_UUID, NULL); + if (uuid == NULL) { + talloc_zfree(msg); + return ENOENT; + } + + uuid = talloc_strdup(mem_ctx, uuid); + talloc_zfree(msg); + if (uuid == NULL) { + return ENOMEM; + } + + *_out = uuid; + + return EOK; +} + +static errno_t +ifp_groups_group_get_members(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char ***_users, + const char ***_groups) +{ + TALLOC_CTX *tmp_ctx; + struct sss_domain_info *domain; + struct ldb_message *group; + struct ldb_message **members; + size_t num_members; + const char *class; + const char **users; + const char **groups; + int num_users; + int num_groups; + int i; + errno_t ret; + const char *attrs[] = {SYSDB_OBJECTCATEGORY, SYSDB_UIDNUM, + SYSDB_GIDNUM, NULL}; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = ifp_groups_group_get(tmp_ctx, sbus_req, ctx, &domain, &group); + if (ret != EOK) { + goto done; + } + + ret = sysdb_asq_search(tmp_ctx, domain, group->dn, NULL, SYSDB_MEMBER, + attrs, &num_members, &members); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to perform ASQ search [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + if (num_members == 0) { + users = NULL; + groups = NULL; + ret = EOK; + goto done; + } + + users = talloc_zero_array(tmp_ctx, const char *, num_members + 1); + if (users == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero_array() failed\n"); + ret = ENOMEM; + goto done; + } + + groups = talloc_zero_array(tmp_ctx, const char *, num_members + 1); + if (groups == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero_array() failed\n"); + ret = ENOMEM; + goto done; + } + + num_users = 0; + num_groups = 0; + for (i = 0; i < num_members; i++) { + class = ldb_msg_find_attr_as_string(members[i], SYSDB_OBJECTCATEGORY, + NULL); + if (class == NULL) { + ret = ERR_INTERNAL; + goto done; + } + + if (strcmp(class, SYSDB_USER_CLASS) == 0) { + users[num_users] = ifp_users_build_path_from_msg(users, domain, + members[i]); + if (users[num_users] == NULL) { + ret = ENOMEM; + goto done; + } + + num_users++; + } else if (strcmp(class, SYSDB_GROUP_CLASS) == 0) { + groups[num_groups] = ifp_groups_build_path_from_msg(groups, + domain, members[i]); + if (groups[num_groups] == NULL) { + ret = ENOMEM; + goto done; + } + + num_groups++; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected object class %s\n", class); + ret = ERR_INTERNAL; + goto done; + } + } + + ret = EOK; + +done: + if (ret == EOK) { + if (_users != NULL) { + *_users = talloc_steal(mem_ctx, users); + } + + if (_groups != NULL) { + *_groups = talloc_steal(mem_ctx, groups); + } + } + + talloc_free(tmp_ctx); + return ret; +} + +errno_t +ifp_groups_group_get_users(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char ***_out) +{ + errno_t ret; + + ret = ifp_groups_group_get_members(mem_ctx, sbus_req, ctx, _out, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to acquire groups members\n"); + return ret; + } + + return EOK; +} + +errno_t +ifp_groups_group_get_groups(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char ***_out) +{ + errno_t ret; + + ret = ifp_groups_group_get_members(mem_ctx, sbus_req, ctx, NULL, _out); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to acquire groups members\n"); + return ret; + } + + return EOK; +} + +errno_t +ifp_cache_list_group(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char ***_out) +{ + return ifp_cache_list(mem_ctx, ctx, IFP_CACHE_GROUP, _out); +} + +errno_t +ifp_cache_list_by_domain_group(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char *domain, + const char ***_out) +{ + return ifp_cache_list_by_domain(mem_ctx, ctx, domain, IFP_CACHE_GROUP, _out); +} + +errno_t +ifp_cache_object_store_group(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + bool *_result) +{ + struct sss_domain_info *domain; + struct ldb_message *group; + errno_t ret; + + ret = ifp_groups_group_get(NULL, sbus_req, ctx, &domain, &group); + if (ret != EOK) { + return ret; + } + + ret = ifp_cache_object_store(domain, group->dn); + talloc_free(group); + + if (ret == EOK) { + *_result = true; + } + + return ret; +} + +errno_t +ifp_cache_object_remove_group(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + bool *_result) +{ + struct sss_domain_info *domain; + struct ldb_message *group; + errno_t ret; + + ret = ifp_groups_group_get(NULL, sbus_req, ctx, &domain, &group); + if (ret != EOK) { + return ret; + } + + ret = ifp_cache_object_remove(domain, group->dn); + talloc_free(group); + + if (ret == EOK) { + *_result = true; + } + + return ret; +} diff --git a/src/responder/ifp/ifp_groups.h b/src/responder/ifp/ifp_groups.h new file mode 100644 index 0000000..114962f --- /dev/null +++ b/src/responder/ifp/ifp_groups.h @@ -0,0 +1,156 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2015 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/>. +*/ + +#ifndef IFP_GROUPS_H_ +#define IFP_GROUPS_H_ + +#include "responder/ifp/ifp_private.h" + +/* Utility functions */ + +char * ifp_groups_build_path_from_msg(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + struct ldb_message *msg); + +/* org.freedesktop.sssd.infopipe.Groups */ + +struct tevent_req * +ifp_groups_find_by_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char *name); + +errno_t +ifp_groups_find_by_name_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char **_path); + +struct tevent_req * +ifp_groups_find_by_id_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + uint32_t id); + +errno_t +ifp_groups_find_by_id_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char **_path); + +struct tevent_req * +ifp_groups_list_by_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char *filter, + uint32_t limit); + +errno_t +ifp_groups_list_by_name_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char ***_paths); + +struct tevent_req * +ifp_groups_list_by_domain_and_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char *domain, + const char *filter, + uint32_t limit); + +errno_t +ifp_groups_list_by_domain_and_name_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char ***_paths); + +/* org.freedesktop.sssd.infopipe.Groups.Group */ + +struct tevent_req * +ifp_groups_group_update_member_list_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx); + +errno_t +ifp_groups_group_update_member_list_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req); + +errno_t +ifp_groups_group_get_name(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char **_out); + +errno_t +ifp_groups_group_get_gid_number(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + uint32_t *_out); + +errno_t +ifp_groups_group_get_unique_id(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char **_out); + +errno_t +ifp_groups_group_get_users(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char ***_out); + +errno_t +ifp_groups_group_get_groups(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char ***_out); + +/* org.freedesktop.sssd.infopipe.Cache */ + +errno_t +ifp_cache_list_group(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char ***_out); + +errno_t +ifp_cache_list_by_domain_group(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char *domain, + const char ***_out); + +/* org.freedesktop.sssd.infopipe.Cache.Object */ + +errno_t +ifp_cache_object_store_group(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + bool *_result); + +errno_t +ifp_cache_object_remove_group(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + bool *_result); + +#endif /* IFP_GROUPS_H_ */ diff --git a/src/responder/ifp/ifp_iface/ifp_iface.c b/src/responder/ifp/ifp_iface/ifp_iface.c new file mode 100644 index 0000000..cbab470 --- /dev/null +++ b/src/responder/ifp/ifp_iface/ifp_iface.c @@ -0,0 +1,274 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2018 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 "responder/common/responder.h" +#include "responder/ifp/ifp_private.h" +#include "responder/ifp/ifp_iface/ifp_iface_async.h" +#include "responder/ifp/ifp_cache.h" +#include "responder/ifp/ifp_components.h" +#include "responder/ifp/ifp_domains.h" +#include "responder/ifp/ifp_groups.h" +#include "responder/ifp/ifp_users.h" + +errno_t +ifp_access_check(struct sbus_request *sbus_req, + struct ifp_ctx *ifp_ctx) +{ + uid_t uid; + errno_t ret; + + /* We allow those special cases to access infopipe. */ + if (sbus_req->sender->uid < 0) { + return EOK; + } + + uid = (uid_t)sbus_req->sender->uid; + + ret = check_allowed_uids(uid, + ifp_ctx->rctx->allowed_uids_count, + ifp_ctx->rctx->allowed_uids); + if (ret == EACCES) { + DEBUG(SSSDBG_MINOR_FAILURE, "User %"PRIi64" not in ACL\n", + sbus_req->sender->uid); + return ret; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot check if user %"PRIi64 + "is present in ACL\n", sbus_req->sender->uid); + return ret; + } + + switch (sbus_req->type) { + case SBUS_REQUEST_PROPERTY_GET: + if (strcmp(sbus_req->interface, "org.freedesktop.sssd.infopipe.Users.User") == 0) { + if (!ifp_is_user_attr_allowed(ifp_ctx, sbus_req->property)) { + DEBUG(SSSDBG_TRACE_ALL, "Attribute %s is not allowed\n", + sbus_req->property); + return EACCES; + } + } + break; + default: + return EOK; + } + + return EOK; +} + +errno_t +ifp_register_sbus_interface(struct sbus_connection *conn, + struct ifp_ctx *ctx) +{ + errno_t ret; + + SBUS_INTERFACE(iface_ifp, + org_freedesktop_sssd_infopipe, + SBUS_METHODS( + SBUS_SYNC(METHOD, org_freedesktop_sssd_infopipe, Ping, ifp_ping, ctx), + SBUS_SYNC(METHOD, org_freedesktop_sssd_infopipe, ListComponents, ifp_list_components, ctx), + SBUS_SYNC(METHOD, org_freedesktop_sssd_infopipe, ListResponders, ifp_list_responders, ctx), + SBUS_SYNC(METHOD, org_freedesktop_sssd_infopipe, ListBackends, ifp_list_backends, ctx), + SBUS_SYNC(METHOD, org_freedesktop_sssd_infopipe, FindMonitor, ifp_find_monitor, ctx), + SBUS_SYNC(METHOD, org_freedesktop_sssd_infopipe, FindResponderByName, ifp_find_responder_by_name, ctx), + SBUS_SYNC(METHOD, org_freedesktop_sssd_infopipe, FindBackendByName, ifp_find_backend_by_name, ctx), + SBUS_ASYNC(METHOD, org_freedesktop_sssd_infopipe, GetUserAttr, ifp_get_user_attr_send, ifp_get_user_attr_recv, ctx), + SBUS_ASYNC(METHOD, org_freedesktop_sssd_infopipe, GetUserGroups, ifp_user_get_groups_send, ifp_user_get_groups_recv, ctx), + SBUS_ASYNC(METHOD, org_freedesktop_sssd_infopipe, FindDomainByName, ifp_find_domain_by_name_send, ifp_find_domain_by_name_recv, ctx), + SBUS_ASYNC(METHOD, org_freedesktop_sssd_infopipe, ListDomains, ifp_list_domains_send, ifp_list_domains_recv, ctx) + ), + SBUS_SIGNALS(SBUS_NO_SIGNALS), + SBUS_PROPERTIES(SBUS_NO_PROPERTIES) + ); + + SBUS_INTERFACE(iface_ifp_components, + org_freedesktop_sssd_infopipe_Components, + SBUS_METHODS(SBUS_NO_METHODS), + SBUS_SIGNALS(SBUS_NO_SIGNALS), + SBUS_PROPERTIES( + SBUS_SYNC(GETTER, org_freedesktop_sssd_infopipe_Components, name, ifp_component_get_name, ctx), + SBUS_SYNC(GETTER, org_freedesktop_sssd_infopipe_Components, debug_level, ifp_component_get_debug_level, ctx), + SBUS_SYNC(GETTER, org_freedesktop_sssd_infopipe_Components, enabled, ifp_component_get_enabled, ctx), + SBUS_SYNC(GETTER, org_freedesktop_sssd_infopipe_Components, type, ifp_component_get_type, ctx), + SBUS_SYNC(GETTER, org_freedesktop_sssd_infopipe_Components, providers, ifp_backend_get_providers, ctx) + ) + ); + + SBUS_INTERFACE(iface_ifp_domains, + org_freedesktop_sssd_infopipe_Domains, + SBUS_METHODS(SBUS_NO_METHODS), + SBUS_SIGNALS(SBUS_NO_SIGNALS), + SBUS_PROPERTIES( + SBUS_SYNC(GETTER, org_freedesktop_sssd_infopipe_Domains, name, ifp_dom_get_name, ctx), + SBUS_SYNC(GETTER, org_freedesktop_sssd_infopipe_Domains, provider, ifp_dom_get_provider, ctx), + SBUS_SYNC(GETTER, org_freedesktop_sssd_infopipe_Domains, primary_servers, ifp_dom_get_primary_servers, ctx), + SBUS_SYNC(GETTER, org_freedesktop_sssd_infopipe_Domains, backup_servers, ifp_dom_get_backup_servers, ctx), + SBUS_SYNC(GETTER, org_freedesktop_sssd_infopipe_Domains, min_id, ifp_dom_get_min_id, ctx), + SBUS_SYNC(GETTER, org_freedesktop_sssd_infopipe_Domains, max_id, ifp_dom_get_max_id, ctx), + SBUS_SYNC(GETTER, org_freedesktop_sssd_infopipe_Domains, realm, ifp_dom_get_realm, ctx), + SBUS_SYNC(GETTER, org_freedesktop_sssd_infopipe_Domains, forest, ifp_dom_get_forest, ctx), + SBUS_SYNC(GETTER, org_freedesktop_sssd_infopipe_Domains, login_format, ifp_dom_get_login_format, ctx), + SBUS_SYNC(GETTER, org_freedesktop_sssd_infopipe_Domains, fully_qualified_name_format, ifp_dom_get_fqdn_format, ctx), + SBUS_SYNC(GETTER, org_freedesktop_sssd_infopipe_Domains, enumerable, ifp_dom_get_enumerable, ctx), + SBUS_SYNC(GETTER, org_freedesktop_sssd_infopipe_Domains, use_fully_qualified_names, ifp_dom_get_use_fqdn, ctx), + SBUS_SYNC(GETTER, org_freedesktop_sssd_infopipe_Domains, subdomain, ifp_dom_get_subdomain, ctx), + SBUS_SYNC(GETTER, org_freedesktop_sssd_infopipe_Domains, parent_domain, ifp_dom_get_parent_domain, ctx) + ) + ); + + SBUS_INTERFACE(iface_ifp_domains_domain, + org_freedesktop_sssd_infopipe_Domains_Domain, + SBUS_METHODS( + SBUS_ASYNC(METHOD, org_freedesktop_sssd_infopipe_Domains_Domain, IsOnline, ifp_domains_domain_is_online_send, ifp_domains_domain_is_online_recv, ctx), + SBUS_ASYNC(METHOD, org_freedesktop_sssd_infopipe_Domains_Domain, ListServices, ifp_domains_domain_list_services_send, ifp_domains_domain_list_services_recv, ctx), + SBUS_ASYNC(METHOD, org_freedesktop_sssd_infopipe_Domains_Domain, ActiveServer, ifp_domains_domain_active_server_send, ifp_domains_domain_active_server_recv, ctx), + SBUS_ASYNC(METHOD, org_freedesktop_sssd_infopipe_Domains_Domain, ListServers, ifp_domains_domain_list_servers_send, ifp_domains_domain_list_servers_recv, ctx), + SBUS_ASYNC(METHOD, org_freedesktop_sssd_infopipe_Domains_Domain, RefreshAccessRules, ifp_domains_domain_refresh_access_rules_send, ifp_domains_domain_refresh_access_rules_recv, ctx) + ), + SBUS_SIGNALS(SBUS_NO_SIGNALS), + SBUS_PROPERTIES(SBUS_NO_PROPERTIES) + ); + + SBUS_INTERFACE(iface_ifp_users, + org_freedesktop_sssd_infopipe_Users, + SBUS_METHODS( + SBUS_ASYNC(METHOD, org_freedesktop_sssd_infopipe_Users, FindByName, ifp_users_find_by_name_send, ifp_users_find_by_name_recv, ctx), + SBUS_ASYNC(METHOD, org_freedesktop_sssd_infopipe_Users, FindByID, ifp_users_find_by_id_send, ifp_users_find_by_id_recv, ctx), + SBUS_ASYNC(METHOD, org_freedesktop_sssd_infopipe_Users, FindByCertificate, ifp_users_find_by_cert_send, ifp_users_find_by_cert_recv, ctx), + SBUS_ASYNC(METHOD, org_freedesktop_sssd_infopipe_Users, ListByCertificate, ifp_users_list_by_cert_send, ifp_users_list_by_cert_recv, ctx), + SBUS_ASYNC(METHOD, org_freedesktop_sssd_infopipe_Users, FindByNameAndCertificate, ifp_users_find_by_name_and_cert_send, ifp_users_find_by_name_and_cert_recv, ctx), + SBUS_ASYNC(METHOD, org_freedesktop_sssd_infopipe_Users, ListByName, ifp_users_list_by_name_send, ifp_users_list_by_attr_recv, ctx), + SBUS_ASYNC(METHOD, org_freedesktop_sssd_infopipe_Users, ListByDomainAndName, ifp_users_list_by_domain_and_name_send, ifp_users_list_by_domain_and_name_recv, ctx), + SBUS_ASYNC(METHOD, org_freedesktop_sssd_infopipe_Users, FindByValidCertificate, ifp_users_find_by_valid_cert_send, ifp_users_find_by_valid_cert_recv, ctx), + SBUS_ASYNC(METHOD, org_freedesktop_sssd_infopipe_Users, ListByAttr, ifp_users_list_by_attr_send, ifp_users_list_by_attr_recv, ctx) + ), + SBUS_SIGNALS(SBUS_NO_SIGNALS), + SBUS_PROPERTIES(SBUS_NO_PROPERTIES) + ); + + SBUS_INTERFACE(iface_ifp_users_user, + org_freedesktop_sssd_infopipe_Users_User, + SBUS_METHODS(SBUS_NO_METHODS), + SBUS_SIGNALS(SBUS_NO_SIGNALS), + SBUS_PROPERTIES( + SBUS_SYNC(GETTER, org_freedesktop_sssd_infopipe_Users_User, name, ifp_users_user_get_name, ctx), + SBUS_SYNC(GETTER, org_freedesktop_sssd_infopipe_Users_User, uidNumber, ifp_users_user_get_uid_number, ctx), + SBUS_SYNC(GETTER, org_freedesktop_sssd_infopipe_Users_User, gidNumber, ifp_users_user_get_gid_number, ctx), + SBUS_SYNC(GETTER, org_freedesktop_sssd_infopipe_Users_User, gecos, ifp_users_user_get_gecos, ctx), + SBUS_SYNC(GETTER, org_freedesktop_sssd_infopipe_Users_User, homeDirectory, ifp_users_user_get_home_directory, ctx), + SBUS_SYNC(GETTER, org_freedesktop_sssd_infopipe_Users_User, loginShell, ifp_users_user_get_login_shell, ctx), + SBUS_SYNC(GETTER, org_freedesktop_sssd_infopipe_Users_User, uniqueID, ifp_users_user_get_unique_id, ctx), + SBUS_SYNC(GETTER, org_freedesktop_sssd_infopipe_Users_User, groups, ifp_users_user_get_groups, ctx), + SBUS_SYNC(GETTER, org_freedesktop_sssd_infopipe_Users_User, domain, ifp_users_user_get_domain, ctx), + SBUS_SYNC(GETTER, org_freedesktop_sssd_infopipe_Users_User, domainname, ifp_users_user_get_domainname, ctx), + SBUS_SYNC(GETTER, org_freedesktop_sssd_infopipe_Users_User, extraAttributes, ifp_users_user_get_extra_attributes, ctx) + ) + ); + + SBUS_INTERFACE(iface_ifp_cache_user, + org_freedesktop_sssd_infopipe_Cache, + SBUS_METHODS( + SBUS_SYNC(METHOD, org_freedesktop_sssd_infopipe_Cache, List, ifp_cache_list_user, ctx), + SBUS_SYNC(METHOD, org_freedesktop_sssd_infopipe_Cache, ListByDomain, ifp_cache_list_by_domain_user, ctx) + ), + SBUS_SIGNALS(SBUS_NO_SIGNALS), + SBUS_PROPERTIES(SBUS_NO_PROPERTIES) + ); + + SBUS_INTERFACE(iface_ifp_cache_object_user, + org_freedesktop_sssd_infopipe_Cache_Object, + SBUS_METHODS( + SBUS_SYNC(METHOD, org_freedesktop_sssd_infopipe_Cache_Object, Store, ifp_cache_object_store_user, ctx), + SBUS_SYNC(METHOD, org_freedesktop_sssd_infopipe_Cache_Object, Remove, ifp_cache_object_remove_user, ctx) + ), + SBUS_SIGNALS(SBUS_NO_SIGNALS), + SBUS_PROPERTIES(SBUS_NO_PROPERTIES) + ); + + SBUS_INTERFACE(iface_ifp_groups, + org_freedesktop_sssd_infopipe_Groups, + SBUS_METHODS( + SBUS_ASYNC(METHOD, org_freedesktop_sssd_infopipe_Groups, FindByName, ifp_groups_find_by_name_send, ifp_groups_find_by_name_recv, ctx), + SBUS_ASYNC(METHOD, org_freedesktop_sssd_infopipe_Groups, FindByID, ifp_groups_find_by_id_send, ifp_groups_find_by_id_recv, ctx), + SBUS_ASYNC(METHOD, org_freedesktop_sssd_infopipe_Groups, ListByName, ifp_groups_list_by_name_send, ifp_groups_list_by_name_recv, ctx), + SBUS_ASYNC(METHOD, org_freedesktop_sssd_infopipe_Groups, ListByDomainAndName, ifp_groups_list_by_domain_and_name_send, ifp_groups_list_by_domain_and_name_recv, ctx) + ), + SBUS_SIGNALS(SBUS_NO_SIGNALS), + SBUS_PROPERTIES(SBUS_NO_PROPERTIES) + ); + + SBUS_INTERFACE(iface_ifp_groups_group, + org_freedesktop_sssd_infopipe_Groups_Group, + SBUS_METHODS( + SBUS_ASYNC(METHOD, org_freedesktop_sssd_infopipe_Groups_Group, UpdateMemberList, ifp_groups_group_update_member_list_send, ifp_groups_group_update_member_list_recv, ctx) + ), + SBUS_SIGNALS(SBUS_NO_SIGNALS), + SBUS_PROPERTIES( + SBUS_SYNC(GETTER, org_freedesktop_sssd_infopipe_Groups_Group, name, ifp_groups_group_get_name, ctx), + SBUS_SYNC(GETTER, org_freedesktop_sssd_infopipe_Groups_Group, gidNumber, ifp_groups_group_get_gid_number, ctx), + SBUS_SYNC(GETTER, org_freedesktop_sssd_infopipe_Groups_Group, uniqueID, ifp_groups_group_get_unique_id, ctx), + SBUS_SYNC(GETTER, org_freedesktop_sssd_infopipe_Groups_Group, users, ifp_groups_group_get_users, ctx), + SBUS_SYNC(GETTER, org_freedesktop_sssd_infopipe_Groups_Group, groups, ifp_groups_group_get_groups, ctx) + ) + ); + + SBUS_INTERFACE(iface_ifp_cache_group, + org_freedesktop_sssd_infopipe_Cache, + SBUS_METHODS( + SBUS_SYNC(METHOD, org_freedesktop_sssd_infopipe_Cache, List, ifp_cache_list_group, ctx), + SBUS_SYNC(METHOD, org_freedesktop_sssd_infopipe_Cache, ListByDomain, ifp_cache_list_by_domain_group, ctx) + ), + SBUS_SIGNALS(SBUS_NO_SIGNALS), + SBUS_PROPERTIES(SBUS_NO_PROPERTIES) + ); + + SBUS_INTERFACE(iface_ifp_cache_object_group, + org_freedesktop_sssd_infopipe_Cache_Object, + SBUS_METHODS( + SBUS_SYNC(METHOD, org_freedesktop_sssd_infopipe_Cache_Object, Store, ifp_cache_object_store_group, ctx), + SBUS_SYNC(METHOD, org_freedesktop_sssd_infopipe_Cache_Object, Remove, ifp_cache_object_remove_group, ctx) + ), + SBUS_SIGNALS(SBUS_NO_SIGNALS), + SBUS_PROPERTIES(SBUS_NO_PROPERTIES) + ); + + struct sbus_path paths[] = { + { IFP_PATH, &iface_ifp }, + { IFP_PATH_DOMAINS, &iface_ifp_domains }, + { IFP_PATH_DOMAINS_TREE, &iface_ifp_domains }, + { IFP_PATH_DOMAINS_TREE, &iface_ifp_domains_domain }, + { IFP_PATH_COMPONENTS_TREE, &iface_ifp_components }, + { IFP_PATH_USERS, &iface_ifp_users }, + { IFP_PATH_USERS, &iface_ifp_cache_user }, + { IFP_PATH_USERS_TREE, &iface_ifp_users_user }, + { IFP_PATH_USERS_TREE, &iface_ifp_cache_object_user }, + { IFP_PATH_GROUPS, &iface_ifp_groups }, + { IFP_PATH_GROUPS, &iface_ifp_cache_group }, + { IFP_PATH_GROUPS_TREE, &iface_ifp_groups_group }, + { IFP_PATH_GROUPS_TREE, &iface_ifp_cache_object_group }, + {NULL, NULL} + }; + + ret = sbus_connection_add_path_map(conn, paths); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to add paths [%d]: %s\n", + ret, sss_strerror(ret)); + } + + return ret; +} diff --git a/src/responder/ifp/ifp_iface/ifp_iface.h b/src/responder/ifp/ifp_iface/ifp_iface.h new file mode 100644 index 0000000..0bf6510 --- /dev/null +++ b/src/responder/ifp/ifp_iface/ifp_iface.h @@ -0,0 +1,40 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2018 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/>. +*/ + +#ifndef _IFP_IFACE_H_ +#define _IFP_IFACE_H_ + +#define IFP_BUS "org.freedesktop.sssd.infopipe" + +#define IFP_PATH "/org/freedesktop/sssd/infopipe" + +#define IFP_PATH_DOMAINS IFP_PATH "/Domains" +#define IFP_PATH_DOMAINS_TREE IFP_PATH_DOMAINS "/*" + +#define IFP_PATH_COMPONENTS IFP_PATH "/Components" +#define IFP_PATH_COMPONENTS_TREE IFP_PATH_COMPONENTS "/*" + +#define IFP_PATH_GROUPS IFP_PATH "/Groups" +#define IFP_PATH_GROUPS_TREE IFP_PATH_GROUPS "/*" + +#define IFP_PATH_USERS IFP_PATH "/Users" +#define IFP_PATH_USERS_TREE IFP_PATH_USERS "/*" + +#endif /* _IFP_IFACE_H_ */ diff --git a/src/responder/ifp/ifp_iface/ifp_iface.xml b/src/responder/ifp/ifp_iface/ifp_iface.xml new file mode 100644 index 0000000..75b4891 --- /dev/null +++ b/src/responder/ifp/ifp_iface/ifp_iface.xml @@ -0,0 +1,253 @@ +<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> +<node> + <interface name="org.freedesktop.sssd.infopipe"> + <annotation name="codegen.Name" value="ifp" /> + <annotation name="codegen.AsyncCaller" value="false" /> + + <method name="Ping"> + <arg name="ping" type="s" direction="in" key="1" /> + <arg name="pong" type="s" direction="out" /> + </method> + + <!-- SSSD components --> + + <method name="ListComponents" key="True"> + <arg name="components" type="ao" direction="out"/> + </method> + + <method name="ListResponders" key="True"> + <arg name="responders" type="ao" direction="out"/> + </method> + + <method name="ListBackends" key="True"> + <arg name="backends" type="ao" direction="out"/> + </method> + + <method name="FindMonitor" key="True"> + <arg name="monitor" type="o" direction="out"/> + </method> + + <method name="FindResponderByName"> + <arg name="name" type="s" direction="in" key="1" /> + <arg name="responder" type="o" direction="out"/> + </method> + + <method name="FindBackendByName"> + <arg name="name" type="s" direction="in" key="1" /> + <arg name="backend" type="o" direction="out"/> + </method> + + <method name="GetUserAttr"> + <annotation name="codegen.CustomOutputHandler" value="true"/> + <arg name="user" type="s" direction="in" /> + <arg name="attr" type="as" direction="in" /> + <arg name="values" type="a{sv}" direction="out"/> + </method> + + <method name="GetUserGroups"> + <arg name="user" type="s" direction="in" key="1" /> + <arg name="values" type="as" direction="out"/> + </method> + + <method name="FindDomainByName"> + <arg name="name" type="s" direction="in" key="1" /> + <arg name="domain" type="o" direction="out"/> + </method> + + <method name="ListDomains" key="True"> + <arg name="domain" type="ao" direction="out"/> + </method> + + </interface> + + <interface name="org.freedesktop.sssd.infopipe.Components"> + <annotation name="codegen.Name" value="ifp_components" /> + <annotation name="codegen.AsyncCaller" value="false" /> + + <property name="name" type="s" access="read" /> + <property name="debug_level" type="u" access="read" /> + <property name="enabled" type="b" access="read" /> + <property name="type" type="s" access="read" /> + + <!-- FIXME: This should be part of Components.Backends interface, onece + SSSD supports multiple interfaces per object path. --> + <property name="providers" type="as" access="read" /> + </interface> + + <interface name="org.freedesktop.sssd.infopipe.Domains"> + <annotation name="codegen.Name" value="ifp_domains" /> + <annotation name="codegen.AsyncCaller" value="false" /> + + <property name="name" type="s" access="read"/> + <property name="provider" type="s" access="read"/> + <property name="primary_servers" type="as" access="read"/> + <property name="backup_servers" type="as" access="read"/> + <property name="min_id" type="u" access="read"/> + <property name="max_id" type="u" access="read"/> + <property name="realm" type="s" access="read"/> + <property name="forest" type="s" access="read"/> + <property name="login_format" type="s" access="read"/> + <property name="fully_qualified_name_format" type="s" access="read"/> + <property name="enumerable" type="b" access="read"/> + <property name="use_fully_qualified_names" type="b" access="read"/> + <property name="subdomain" type="b" access="read"/> + <property name="parent_domain" type="o" access="read"/> + </interface> + + <interface name="org.freedesktop.sssd.infopipe.Domains.Domain"> + <annotation name="codegen.Name" value="ifp_domain" /> + <annotation name="codegen.AsyncCaller" value="false" /> + + <method name="IsOnline" key="True"> + <arg name="status" type="b" direction="out" /> + </method> + + <method name="ListServices" key="True"> + <arg name="services" type="as" direction="out" /> + </method> + + <method name="ActiveServer"> + <arg name="service" type="s" direction="in" key="1" /> + <arg name="server" type="s" direction="out" /> + </method> + + <method name="ListServers"> + <arg name="service_name" type="s" direction="in" key="1" /> + <arg name="servers" type="as" direction="out" /> + </method> + + <method name="RefreshAccessRules" key="True" /> + </interface> + + <interface name="org.freedesktop.sssd.infopipe.Cache"> + <annotation name="codegen.Name" value="ifp_cache" /> + <annotation name="codegen.AsyncCaller" value="false" /> + + <method name="List" key="True"> + <arg name="result" type="ao" direction="out" /> + </method> + <method name="ListByDomain"> + <arg name="domain_name" type="s" direction="in" key="1" /> + <arg name="result" type="ao" direction="out"/> + </method> + </interface> + + <interface name="org.freedesktop.sssd.infopipe.Cache.Object"> + <annotation name="codegen.Name" value="ifp_cache_object" /> + <annotation name="codegen.AsyncCaller" value="false" /> + + <method name="Store" key="True"> + <arg name="result" type="b" direction="out" /> + </method> + <method name="Remove" key="True"> + <arg name="result" type="b" direction="out" /> + </method> + </interface> + + <interface name="org.freedesktop.sssd.infopipe.Users"> + <annotation name="codegen.Name" value="ifp_users" /> + <annotation name="codegen.AsyncCaller" value="false" /> + + <method name="FindByName"> + <arg name="name" type="s" direction="in" key="1" /> + <arg name="result" type="o" direction="out" /> + </method> + <method name="FindByID"> + <arg name="id" type="u" direction="in" key="1" /> + <arg name="result" type="o" direction="out" /> + </method> + <method name="FindByCertificate"> + <arg name="pem_cert" type="s" direction="in" /> + <arg name="result" type="o" direction="out" /> + </method> + <method name="ListByCertificate"> + <arg name="pem_cert" type="s" direction="in" /> + <arg name="limit" type="u" direction="in" /> + <arg name="result" type="ao" direction="out" /> + </method> + <method name="FindByNameAndCertificate"> + <arg name="name" type="s" direction="in" /> + <arg name="pem_cert" type="s" direction="in" /> + <arg name="result" type="o" direction="out" /> + </method> + <method name="ListByName"> + <arg name="name_filter" type="s" direction="in" key="1" /> + <arg name="limit" type="u" direction="in" key="2" /> + <arg name="result" type="ao" direction="out" /> + </method> + <method name="ListByDomainAndName"> + <arg name="domain_name" type="s" direction="in" key="1" /> + <arg name="name_filter" type="s" direction="in" key="2" /> + <arg name="limit" type="u" direction="in" key="3" /> + <arg name="result" type="ao" direction="out"/> + </method> + <method name="FindByValidCertificate"> + <arg name="pem_cert" type="s" direction="in" /> + <arg name="result" type="o" direction="out" /> + </method> + <method name="ListByAttr"> + <arg name="attribute" type="s" direction="in" key="1" /> + <arg name="attr_filter" type="s" direction="in" key="2" /> + <arg name="limit" type="u" direction="in" key="3" /> + <arg name="result" type="ao" direction="out" /> + </method> + </interface> + + <interface name="org.freedesktop.sssd.infopipe.Users.User"> + <annotation name="codegen.Name" value="ifp_user" /> + <annotation name="codegen.AsyncCaller" value="false" /> + + <method name="UpdateGroupsList" key="True" /> + + <property name="name" type="s" access="read" /> + <property name="uidNumber" type="u" access="read" /> + <property name="gidNumber" type="u" access="read" /> + <property name="gecos" type="s" access="read" /> + <property name="homeDirectory" type="s" access="read" /> + <property name="loginShell" type="s" access="read" /> + <property name="uniqueID" type="s" access="read" /> + <property name="groups" type="ao" access="read" /> + <property name="domain" type="o" access="read" /> + <property name="domainname" type="s" access="read" /> + <property name="extraAttributes" type="ifp_extra" access="read" /> + </interface> + + <interface name="org.freedesktop.sssd.infopipe.Groups"> + <annotation name="codegen.Name" value="ifp_groups" /> + <annotation name="codegen.AsyncCaller" value="false" /> + + <method name="FindByName"> + <arg name="name" type="s" direction="in" key="1" /> + <arg name="result" type="o" direction="out" /> + </method> + <method name="FindByID"> + <arg name="id" type="u" direction="in" key="1" /> + <arg name="result" type="o" direction="out" /> + </method> + <method name="ListByName"> + <arg name="name_filter" type="s" direction="in" key="1" /> + <arg name="limit" type="u" direction="in" key="2" /> + <arg name="result" type="ao" direction="out" /> + </method> + <method name="ListByDomainAndName"> + <arg name="domain_name" type="s" direction="in" key="1" /> + <arg name="name_filter" type="s" direction="in" key="2" /> + <arg name="limit" type="u" direction="in" key="3" /> + <arg name="result" type="ao" direction="out"/> + </method> + </interface> + + <interface name="org.freedesktop.sssd.infopipe.Groups.Group"> + <annotation name="codegen.Name" value="ifp_group" /> + <annotation name="codegen.AsyncCaller" value="false" /> + + <method name="UpdateMemberList" key="True" /> + + <property name="name" type="s" access="read" /> + <property name="gidNumber" type="u" access="read" /> + <property name="uniqueID" type="s" access="read" /> + <property name="users" type="ao" access="read" /> + <property name="groups" type="ao" access="read" /> + </interface> +</node> diff --git a/src/responder/ifp/ifp_iface/ifp_iface_async.h b/src/responder/ifp/ifp_iface/ifp_iface_async.h new file mode 100644 index 0000000..bdf737d --- /dev/null +++ b/src/responder/ifp/ifp_iface/ifp_iface_async.h @@ -0,0 +1,28 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2018 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/>. +*/ + +#ifndef _IFP_IFACE_ASYNC_H_ +#define _IFP_IFACE_ASYNC_H_ + +#include "responder/ifp/ifp_iface/sbus_ifp_server.h" +#include "responder/ifp/ifp_iface/sbus_ifp_client_async.h" +#include "responder/ifp/ifp_iface/ifp_iface.h" + +#endif /* _IFP_IFACE_ASYNC_H_ */ diff --git a/src/responder/ifp/ifp_iface/ifp_iface_sync.h b/src/responder/ifp/ifp_iface/ifp_iface_sync.h new file mode 100644 index 0000000..804e0dd --- /dev/null +++ b/src/responder/ifp/ifp_iface/ifp_iface_sync.h @@ -0,0 +1,27 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2018 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/>. +*/ + +#ifndef _IFP_IFACE_SYNC_H_ +#define _IFP_IFACE_SYNC_H_ + +#include "responder/ifp/ifp_iface/sbus_ifp_client_sync.h" +#include "responder/ifp/ifp_iface/ifp_iface.h" + +#endif /* _IFP_IFACE_SYNC_H_ */ diff --git a/src/responder/ifp/ifp_iface/ifp_iface_types.c b/src/responder/ifp/ifp_iface/ifp_iface_types.c new file mode 100644 index 0000000..ad18304 --- /dev/null +++ b/src/responder/ifp/ifp_iface/ifp_iface_types.c @@ -0,0 +1,225 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2017 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 <string.h> +#include <stdint.h> +#include <talloc.h> +#include <dbus/dbus.h> + +#include "util/util.h" +#include "responder/ifp/ifp_iface/ifp_iface_types.h" +#include "sbus/interface/sbus_iterator_readers.h" +#include "sbus/interface/sbus_iterator_writers.h" + +/** + * D-Bus signature: a{sas} + */ +errno_t sbus_iterator_read_ifp_extra(TALLOC_CTX *mem_ctx, + DBusMessageIter *iterator, + hash_table_t **_table) +{ + DBusMessageIter iter_array; + DBusMessageIter iter_dict; + hash_table_t *table; + hash_key_t hkey; + hash_value_t hvalue; + char **values; + char *key; + int arg_type; + errno_t ret; + int count; + int hret; + int i; + + ret = sss_hash_create(mem_ctx, 0, &table); + if (ret != EOK) { + return ret; + } + + arg_type = dbus_message_iter_get_arg_type(iterator); + if (arg_type != DBUS_TYPE_ARRAY) { + ret = ERR_SBUS_INVALID_TYPE; + goto done; + } + + count = dbus_message_iter_get_element_count(iterator); + dbus_message_iter_recurse(iterator, &iter_array); + + for (i = 0; i < count; i++) { + arg_type = dbus_message_iter_get_arg_type(&iter_array); + if (arg_type != DBUS_TYPE_DICT_ENTRY) { + ret = ERR_SBUS_INVALID_TYPE; + goto done; + } + + dbus_message_iter_recurse(&iter_array, &iter_dict); + + ret = sbus_iterator_read_S(table, &iter_dict, &key); + if (ret != EOK) { + goto done; + } + + ret = sbus_iterator_read_aS(table, &iter_dict, &values); + if (ret != EOK) { + goto done; + } + + hkey.type = HASH_KEY_STRING; + hkey.str = key; + + hvalue.type = HASH_VALUE_PTR; + hvalue.ptr = values; + + hret = hash_enter(table, &hkey, &hvalue); + if (hret != HASH_SUCCESS) { + ret = EIO; + goto done; + } + + /* dhash will duplicate the key internally */ + talloc_free(key); + + dbus_message_iter_next(&iter_array); + } + + *_table = table; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(table); + } + + return ret; +} + +/** + * D-Bus signature: a{sas} + */ +errno_t sbus_iterator_write_ifp_extra(DBusMessageIter *iterator, + hash_table_t *table) +{ + DBusMessageIter it_array; + DBusMessageIter it_dict; + struct hash_iter_context_t *table_iter = NULL; + bool in_array = false; + bool in_dict = false; + hash_entry_t *entry; + const char **values; + dbus_bool_t dbret; + errno_t ret; + + dbret = dbus_message_iter_open_container(iterator, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_ARRAY_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &it_array); + if (!dbret) { + ret = EIO; + goto done; + } + + in_array = true; + + if (table == NULL) { + dbret = dbus_message_iter_close_container(iterator, &it_array); + if (!dbret) { + ret = EIO; + goto done; + } + + in_array = false; + ret = EOK; + goto done; + } + + table_iter = new_hash_iter_context(table); + if (table_iter == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "new_hash_iter_context failed.\n"); + ret = EINVAL; + goto done; + } + + while ((entry = table_iter->next(table_iter)) != NULL) { + if (entry->key.type != HASH_KEY_STRING || entry->key.str == NULL + || entry->value.type != HASH_VALUE_PTR + || entry->value.ptr == NULL) { + continue; + } + + dbret = dbus_message_iter_open_container(&it_array, + DBUS_TYPE_DICT_ENTRY, NULL, + &it_dict); + if (!dbret) { + ret = EIO; + goto done; + } + + in_dict = true; + + ret = sbus_iterator_write_s(&it_dict, entry->key.str); + if (ret != EOK) { + goto done; + } + + values = entry->value.ptr; + ret = sbus_iterator_write_as(&it_dict, values); + if (ret != EOK) { + goto done; + } + + dbret = dbus_message_iter_close_container(&it_array, &it_dict); + if (!dbret) { + ret = EIO; + goto done; + } + + in_dict = false; + } + + dbret = dbus_message_iter_close_container(iterator, &it_array); + if (!dbret) { + ret = EIO; + goto done; + } + + in_array = false; + ret = EOK; + +done: + if (ret != EOK) { + if (in_dict) { + dbus_message_iter_abandon_container(&it_array, &it_dict); + } + + if (in_array) { + dbus_message_iter_abandon_container(iterator, &it_array); + } + } + + if (table_iter != NULL) { + talloc_free(table_iter); + } + + return ret; +} diff --git a/src/responder/ifp/ifp_iface/ifp_iface_types.h b/src/responder/ifp/ifp_iface/ifp_iface_types.h new file mode 100644 index 0000000..0a3cbd0 --- /dev/null +++ b/src/responder/ifp/ifp_iface/ifp_iface_types.h @@ -0,0 +1,35 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2017 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/>. +*/ + +#ifndef _IFP_IFACE_CUSTOM_TYPES_H_ +#define _IFP_IFACE_CUSTOM_TYPES_H_ + +#include <talloc.h> +#include <dhash.h> +#include <dbus/dbus.h> + +errno_t sbus_iterator_read_ifp_extra(TALLOC_CTX *mem_ctx, + DBusMessageIter *iterator, + hash_table_t **_table); + +errno_t sbus_iterator_write_ifp_extra(DBusMessageIter *iterator, + hash_table_t *table); + +#endif /* _IFP_IFACE_CUSTOM_TYPES_H_ */ diff --git a/src/responder/ifp/ifp_iface/sbus_ifp_arguments.c b/src/responder/ifp/ifp_iface/sbus_ifp_arguments.c new file mode 100644 index 0000000..031db83 --- /dev/null +++ b/src/responder/ifp/ifp_iface/sbus_ifp_arguments.c @@ -0,0 +1,397 @@ +/* + Generated by sbus code generator + + Copyright (C) 2017 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 <stdint.h> +#include <talloc.h> +#include <stdbool.h> +#include <dbus/dbus.h> + +#include "sbus/interface/sbus_iterator_readers.h" +#include "sbus/interface/sbus_iterator_writers.h" +#include "responder/ifp/ifp_iface/sbus_ifp_arguments.h" + +errno_t _sbus_ifp_invoker_read_ao + (TALLOC_CTX *mem_ctx, + DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_ao *args) +{ + errno_t ret; + + ret = sbus_iterator_read_ao(mem_ctx, iter, &args->arg0); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +errno_t _sbus_ifp_invoker_write_ao + (DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_ao *args) +{ + errno_t ret; + + ret = sbus_iterator_write_ao(iter, args->arg0); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +errno_t _sbus_ifp_invoker_read_as + (TALLOC_CTX *mem_ctx, + DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_as *args) +{ + errno_t ret; + + ret = sbus_iterator_read_as(mem_ctx, iter, &args->arg0); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +errno_t _sbus_ifp_invoker_write_as + (DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_as *args) +{ + errno_t ret; + + ret = sbus_iterator_write_as(iter, args->arg0); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +errno_t _sbus_ifp_invoker_read_b + (TALLOC_CTX *mem_ctx, + DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_b *args) +{ + errno_t ret; + + ret = sbus_iterator_read_b(iter, &args->arg0); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +errno_t _sbus_ifp_invoker_write_b + (DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_b *args) +{ + errno_t ret; + + ret = sbus_iterator_write_b(iter, args->arg0); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +errno_t _sbus_ifp_invoker_read_ifp_extra + (TALLOC_CTX *mem_ctx, + DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_ifp_extra *args) +{ + errno_t ret; + + ret = sbus_iterator_read_ifp_extra(mem_ctx, iter, &args->arg0); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +errno_t _sbus_ifp_invoker_write_ifp_extra + (DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_ifp_extra *args) +{ + errno_t ret; + + ret = sbus_iterator_write_ifp_extra(iter, args->arg0); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +errno_t _sbus_ifp_invoker_read_o + (TALLOC_CTX *mem_ctx, + DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_o *args) +{ + errno_t ret; + + ret = sbus_iterator_read_o(mem_ctx, iter, &args->arg0); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +errno_t _sbus_ifp_invoker_write_o + (DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_o *args) +{ + errno_t ret; + + ret = sbus_iterator_write_o(iter, args->arg0); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +errno_t _sbus_ifp_invoker_read_s + (TALLOC_CTX *mem_ctx, + DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_s *args) +{ + errno_t ret; + + ret = sbus_iterator_read_s(mem_ctx, iter, &args->arg0); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +errno_t _sbus_ifp_invoker_write_s + (DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_s *args) +{ + errno_t ret; + + ret = sbus_iterator_write_s(iter, args->arg0); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +errno_t _sbus_ifp_invoker_read_sas + (TALLOC_CTX *mem_ctx, + DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_sas *args) +{ + errno_t ret; + + ret = sbus_iterator_read_s(mem_ctx, iter, &args->arg0); + if (ret != EOK) { + return ret; + } + + ret = sbus_iterator_read_as(mem_ctx, iter, &args->arg1); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +errno_t _sbus_ifp_invoker_write_sas + (DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_sas *args) +{ + errno_t ret; + + ret = sbus_iterator_write_s(iter, args->arg0); + if (ret != EOK) { + return ret; + } + + ret = sbus_iterator_write_as(iter, args->arg1); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +errno_t _sbus_ifp_invoker_read_ss + (TALLOC_CTX *mem_ctx, + DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_ss *args) +{ + errno_t ret; + + ret = sbus_iterator_read_s(mem_ctx, iter, &args->arg0); + if (ret != EOK) { + return ret; + } + + ret = sbus_iterator_read_s(mem_ctx, iter, &args->arg1); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +errno_t _sbus_ifp_invoker_write_ss + (DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_ss *args) +{ + errno_t ret; + + ret = sbus_iterator_write_s(iter, args->arg0); + if (ret != EOK) { + return ret; + } + + ret = sbus_iterator_write_s(iter, args->arg1); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +errno_t _sbus_ifp_invoker_read_ssu + (TALLOC_CTX *mem_ctx, + DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_ssu *args) +{ + errno_t ret; + + ret = sbus_iterator_read_s(mem_ctx, iter, &args->arg0); + if (ret != EOK) { + return ret; + } + + ret = sbus_iterator_read_s(mem_ctx, iter, &args->arg1); + if (ret != EOK) { + return ret; + } + + ret = sbus_iterator_read_u(iter, &args->arg2); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +errno_t _sbus_ifp_invoker_write_ssu + (DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_ssu *args) +{ + errno_t ret; + + ret = sbus_iterator_write_s(iter, args->arg0); + if (ret != EOK) { + return ret; + } + + ret = sbus_iterator_write_s(iter, args->arg1); + if (ret != EOK) { + return ret; + } + + ret = sbus_iterator_write_u(iter, args->arg2); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +errno_t _sbus_ifp_invoker_read_su + (TALLOC_CTX *mem_ctx, + DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_su *args) +{ + errno_t ret; + + ret = sbus_iterator_read_s(mem_ctx, iter, &args->arg0); + if (ret != EOK) { + return ret; + } + + ret = sbus_iterator_read_u(iter, &args->arg1); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +errno_t _sbus_ifp_invoker_write_su + (DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_su *args) +{ + errno_t ret; + + ret = sbus_iterator_write_s(iter, args->arg0); + if (ret != EOK) { + return ret; + } + + ret = sbus_iterator_write_u(iter, args->arg1); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +errno_t _sbus_ifp_invoker_read_u + (TALLOC_CTX *mem_ctx, + DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_u *args) +{ + errno_t ret; + + ret = sbus_iterator_read_u(iter, &args->arg0); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +errno_t _sbus_ifp_invoker_write_u + (DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_u *args) +{ + errno_t ret; + + ret = sbus_iterator_write_u(iter, args->arg0); + if (ret != EOK) { + return ret; + } + + return EOK; +} diff --git a/src/responder/ifp/ifp_iface/sbus_ifp_arguments.h b/src/responder/ifp/ifp_iface/sbus_ifp_arguments.h new file mode 100644 index 0000000..4b88b78 --- /dev/null +++ b/src/responder/ifp/ifp_iface/sbus_ifp_arguments.h @@ -0,0 +1,201 @@ +/* + Generated by sbus code generator + + Copyright (C) 2017 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/>. +*/ + +#ifndef _SBUS_IFP_ARGUMENTS_H_ +#define _SBUS_IFP_ARGUMENTS_H_ + +#include <errno.h> +#include <stdint.h> +#include <talloc.h> +#include <stdbool.h> +#include <dbus/dbus.h> + +#include "responder/ifp/ifp_iface/ifp_iface_types.h" + +struct _sbus_ifp_invoker_args_ao { + const char ** arg0; +}; + +errno_t +_sbus_ifp_invoker_read_ao + (TALLOC_CTX *mem_ctx, + DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_ao *args); + +errno_t +_sbus_ifp_invoker_write_ao + (DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_ao *args); + +struct _sbus_ifp_invoker_args_as { + const char ** arg0; +}; + +errno_t +_sbus_ifp_invoker_read_as + (TALLOC_CTX *mem_ctx, + DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_as *args); + +errno_t +_sbus_ifp_invoker_write_as + (DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_as *args); + +struct _sbus_ifp_invoker_args_b { + bool arg0; +}; + +errno_t +_sbus_ifp_invoker_read_b + (TALLOC_CTX *mem_ctx, + DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_b *args); + +errno_t +_sbus_ifp_invoker_write_b + (DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_b *args); + +struct _sbus_ifp_invoker_args_ifp_extra { + hash_table_t * arg0; +}; + +errno_t +_sbus_ifp_invoker_read_ifp_extra + (TALLOC_CTX *mem_ctx, + DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_ifp_extra *args); + +errno_t +_sbus_ifp_invoker_write_ifp_extra + (DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_ifp_extra *args); + +struct _sbus_ifp_invoker_args_o { + const char * arg0; +}; + +errno_t +_sbus_ifp_invoker_read_o + (TALLOC_CTX *mem_ctx, + DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_o *args); + +errno_t +_sbus_ifp_invoker_write_o + (DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_o *args); + +struct _sbus_ifp_invoker_args_s { + const char * arg0; +}; + +errno_t +_sbus_ifp_invoker_read_s + (TALLOC_CTX *mem_ctx, + DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_s *args); + +errno_t +_sbus_ifp_invoker_write_s + (DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_s *args); + +struct _sbus_ifp_invoker_args_sas { + const char * arg0; + const char ** arg1; +}; + +errno_t +_sbus_ifp_invoker_read_sas + (TALLOC_CTX *mem_ctx, + DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_sas *args); + +errno_t +_sbus_ifp_invoker_write_sas + (DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_sas *args); + +struct _sbus_ifp_invoker_args_ss { + const char * arg0; + const char * arg1; +}; + +errno_t +_sbus_ifp_invoker_read_ss + (TALLOC_CTX *mem_ctx, + DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_ss *args); + +errno_t +_sbus_ifp_invoker_write_ss + (DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_ss *args); + +struct _sbus_ifp_invoker_args_ssu { + const char * arg0; + const char * arg1; + uint32_t arg2; +}; + +errno_t +_sbus_ifp_invoker_read_ssu + (TALLOC_CTX *mem_ctx, + DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_ssu *args); + +errno_t +_sbus_ifp_invoker_write_ssu + (DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_ssu *args); + +struct _sbus_ifp_invoker_args_su { + const char * arg0; + uint32_t arg1; +}; + +errno_t +_sbus_ifp_invoker_read_su + (TALLOC_CTX *mem_ctx, + DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_su *args); + +errno_t +_sbus_ifp_invoker_write_su + (DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_su *args); + +struct _sbus_ifp_invoker_args_u { + uint32_t arg0; +}; + +errno_t +_sbus_ifp_invoker_read_u + (TALLOC_CTX *mem_ctx, + DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_u *args); + +errno_t +_sbus_ifp_invoker_write_u + (DBusMessageIter *iter, + struct _sbus_ifp_invoker_args_u *args); + +#endif /* _SBUS_IFP_ARGUMENTS_H_ */ diff --git a/src/responder/ifp/ifp_iface/sbus_ifp_client_async.c b/src/responder/ifp/ifp_iface/sbus_ifp_client_async.c new file mode 100644 index 0000000..792061c --- /dev/null +++ b/src/responder/ifp/ifp_iface/sbus_ifp_client_async.c @@ -0,0 +1,30 @@ +/* + Generated by sbus code generator + + Copyright (C) 2017 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 <talloc.h> +#include <tevent.h> +#include <dbus/dbus.h> + +#include "sbus/sbus_private.h" +#include "sbus/interface/sbus_iterator_readers.h" +#include "sbus/interface_dbus/sbus_dbus_client_async.h" +#include "responder/ifp/ifp_iface/sbus_ifp_arguments.h" +#include "responder/ifp/ifp_iface/sbus_ifp_keygens.h" +#include "responder/ifp/ifp_iface/sbus_ifp_client_properties.h" diff --git a/src/responder/ifp/ifp_iface/sbus_ifp_client_async.h b/src/responder/ifp/ifp_iface/sbus_ifp_client_async.h new file mode 100644 index 0000000..eb7844b --- /dev/null +++ b/src/responder/ifp/ifp_iface/sbus_ifp_client_async.h @@ -0,0 +1,31 @@ +/* + Generated by sbus code generator + + Copyright (C) 2017 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/>. +*/ + +#ifndef _SBUS_IFP_CLIENT_ASYNC_H_ +#define _SBUS_IFP_CLIENT_ASYNC_H_ + +#include <errno.h> +#include <talloc.h> +#include <tevent.h> + +#include "sbus/sbus.h" +#include "responder/ifp/ifp_iface/sbus_ifp_client_properties.h" +#include "responder/ifp/ifp_iface/ifp_iface_types.h" + +#endif /* _SBUS_IFP_CLIENT_ASYNC_H_ */ diff --git a/src/responder/ifp/ifp_iface/sbus_ifp_client_properties.h b/src/responder/ifp/ifp_iface/sbus_ifp_client_properties.h new file mode 100644 index 0000000..deb3ade --- /dev/null +++ b/src/responder/ifp/ifp_iface/sbus_ifp_client_properties.h @@ -0,0 +1,180 @@ +/* + Generated by sbus code generator + + Copyright (C) 2017 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/>. +*/ + +#ifndef _SBUS_IFP_CLIENT_PROPERTIES_H_ +#define _SBUS_IFP_CLIENT_PROPERTIES_H_ + +#include <stdint.h> +#include <stdbool.h> + +#include "responder/ifp/ifp_iface/ifp_iface_types.h" + +struct sbus_all_ifp_components { + struct { + bool is_set; + uint32_t value; + } debug_level; + struct { + bool is_set; + bool value; + } enabled; + struct { + bool is_set; + const char * value; + } name; + struct { + bool is_set; + const char ** value; + } providers; + struct { + bool is_set; + const char * value; + } type; +}; + +struct sbus_all_ifp_domains { + struct { + bool is_set; + const char ** value; + } backup_servers; + struct { + bool is_set; + bool value; + } enumerable; + struct { + bool is_set; + const char * value; + } forest; + struct { + bool is_set; + const char * value; + } fully_qualified_name_format; + struct { + bool is_set; + const char * value; + } login_format; + struct { + bool is_set; + uint32_t value; + } max_id; + struct { + bool is_set; + uint32_t value; + } min_id; + struct { + bool is_set; + const char * value; + } name; + struct { + bool is_set; + const char * value; + } parent_domain; + struct { + bool is_set; + const char ** value; + } primary_servers; + struct { + bool is_set; + const char * value; + } provider; + struct { + bool is_set; + const char * value; + } realm; + struct { + bool is_set; + bool value; + } subdomain; + struct { + bool is_set; + bool value; + } use_fully_qualified_names; +}; + +struct sbus_all_ifp_group { + struct { + bool is_set; + uint32_t value; + } gidNumber; + struct { + bool is_set; + const char ** value; + } groups; + struct { + bool is_set; + const char * value; + } name; + struct { + bool is_set; + const char * value; + } uniqueID; + struct { + bool is_set; + const char ** value; + } users; +}; + +struct sbus_all_ifp_user { + struct { + bool is_set; + const char * value; + } domain; + struct { + bool is_set; + const char * value; + } domainname; + struct { + bool is_set; + hash_table_t * value; + } extraAttributes; + struct { + bool is_set; + const char * value; + } gecos; + struct { + bool is_set; + uint32_t value; + } gidNumber; + struct { + bool is_set; + const char ** value; + } groups; + struct { + bool is_set; + const char * value; + } homeDirectory; + struct { + bool is_set; + const char * value; + } loginShell; + struct { + bool is_set; + const char * value; + } name; + struct { + bool is_set; + uint32_t value; + } uidNumber; + struct { + bool is_set; + const char * value; + } uniqueID; +}; + +#endif /* _SBUS_IFP_CLIENT_PROPERTIES_H_ */ diff --git a/src/responder/ifp/ifp_iface/sbus_ifp_client_sync.c b/src/responder/ifp/ifp_iface/sbus_ifp_client_sync.c new file mode 100644 index 0000000..e450233 --- /dev/null +++ b/src/responder/ifp/ifp_iface/sbus_ifp_client_sync.c @@ -0,0 +1,2304 @@ +/* + Generated by sbus code generator + + Copyright (C) 2017 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 <talloc.h> +#include <dbus/dbus.h> + +#include "sbus/sbus_sync.h" +#include "sbus/sbus_sync_private.h" +#include "sbus/sbus_message.h" +#include "sbus/interface/sbus_iterator_readers.h" +#include "sbus/interface_dbus/sbus_dbus_client_sync.h" +#include "responder/ifp/ifp_iface/sbus_ifp_arguments.h" +#include "responder/ifp/ifp_iface/sbus_ifp_client_properties.h" + +static errno_t +sbus_method_in__out_ + (struct sbus_sync_connection *conn, + const char *bus, + const char *path, + const char *iface, + const char *method) +{ + TALLOC_CTX *tmp_ctx; + DBusMessage *reply; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + + ret = sbus_sync_call_method(tmp_ctx, conn, NULL, NULL, + bus, path, iface, method, NULL, &reply); + if (ret != EOK) { + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +sbus_method_in__out_ao + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *bus, + const char *path, + const char *iface, + const char *method, + const char *** _arg0) +{ + TALLOC_CTX *tmp_ctx; + struct _sbus_ifp_invoker_args_ao *out; + DBusMessage *reply; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + out = talloc_zero(tmp_ctx, struct _sbus_ifp_invoker_args_ao); + if (out == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to allocate space for output parameters!\n"); + ret = ENOMEM; + goto done; + } + + + ret = sbus_sync_call_method(tmp_ctx, conn, NULL, NULL, + bus, path, iface, method, NULL, &reply); + if (ret != EOK) { + goto done; + } + + ret = sbus_read_output(out, reply, (sbus_invoker_reader_fn)_sbus_ifp_invoker_read_ao, out); + if (ret != EOK) { + goto done; + } + + *_arg0 = talloc_steal(mem_ctx, out->arg0); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +sbus_method_in__out_as + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *bus, + const char *path, + const char *iface, + const char *method, + const char *** _arg0) +{ + TALLOC_CTX *tmp_ctx; + struct _sbus_ifp_invoker_args_as *out; + DBusMessage *reply; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + out = talloc_zero(tmp_ctx, struct _sbus_ifp_invoker_args_as); + if (out == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to allocate space for output parameters!\n"); + ret = ENOMEM; + goto done; + } + + + ret = sbus_sync_call_method(tmp_ctx, conn, NULL, NULL, + bus, path, iface, method, NULL, &reply); + if (ret != EOK) { + goto done; + } + + ret = sbus_read_output(out, reply, (sbus_invoker_reader_fn)_sbus_ifp_invoker_read_as, out); + if (ret != EOK) { + goto done; + } + + *_arg0 = talloc_steal(mem_ctx, out->arg0); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +sbus_method_in__out_b + (struct sbus_sync_connection *conn, + const char *bus, + const char *path, + const char *iface, + const char *method, + bool* _arg0) +{ + TALLOC_CTX *tmp_ctx; + struct _sbus_ifp_invoker_args_b *out; + DBusMessage *reply; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + out = talloc_zero(tmp_ctx, struct _sbus_ifp_invoker_args_b); + if (out == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to allocate space for output parameters!\n"); + ret = ENOMEM; + goto done; + } + + + ret = sbus_sync_call_method(tmp_ctx, conn, NULL, NULL, + bus, path, iface, method, NULL, &reply); + if (ret != EOK) { + goto done; + } + + ret = sbus_read_output(out, reply, (sbus_invoker_reader_fn)_sbus_ifp_invoker_read_b, out); + if (ret != EOK) { + goto done; + } + + *_arg0 = out->arg0; + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +sbus_method_in__out_o + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *bus, + const char *path, + const char *iface, + const char *method, + const char ** _arg0) +{ + TALLOC_CTX *tmp_ctx; + struct _sbus_ifp_invoker_args_o *out; + DBusMessage *reply; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + out = talloc_zero(tmp_ctx, struct _sbus_ifp_invoker_args_o); + if (out == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to allocate space for output parameters!\n"); + ret = ENOMEM; + goto done; + } + + + ret = sbus_sync_call_method(tmp_ctx, conn, NULL, NULL, + bus, path, iface, method, NULL, &reply); + if (ret != EOK) { + goto done; + } + + ret = sbus_read_output(out, reply, (sbus_invoker_reader_fn)_sbus_ifp_invoker_read_o, out); + if (ret != EOK) { + goto done; + } + + *_arg0 = talloc_steal(mem_ctx, out->arg0); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +sbus_method_in_s_out_ao + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *bus, + const char *path, + const char *iface, + const char *method, + const char * arg0, + const char *** _arg0) +{ + TALLOC_CTX *tmp_ctx; + struct _sbus_ifp_invoker_args_s in; + struct _sbus_ifp_invoker_args_ao *out; + DBusMessage *reply; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + out = talloc_zero(tmp_ctx, struct _sbus_ifp_invoker_args_ao); + if (out == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to allocate space for output parameters!\n"); + ret = ENOMEM; + goto done; + } + + in.arg0 = arg0; + + ret = sbus_sync_call_method(tmp_ctx, conn, NULL, + (sbus_invoker_writer_fn)_sbus_ifp_invoker_write_s, + bus, path, iface, method, &in, &reply); + if (ret != EOK) { + goto done; + } + + ret = sbus_read_output(out, reply, (sbus_invoker_reader_fn)_sbus_ifp_invoker_read_ao, out); + if (ret != EOK) { + goto done; + } + + *_arg0 = talloc_steal(mem_ctx, out->arg0); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +sbus_method_in_s_out_as + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *bus, + const char *path, + const char *iface, + const char *method, + const char * arg0, + const char *** _arg0) +{ + TALLOC_CTX *tmp_ctx; + struct _sbus_ifp_invoker_args_s in; + struct _sbus_ifp_invoker_args_as *out; + DBusMessage *reply; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + out = talloc_zero(tmp_ctx, struct _sbus_ifp_invoker_args_as); + if (out == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to allocate space for output parameters!\n"); + ret = ENOMEM; + goto done; + } + + in.arg0 = arg0; + + ret = sbus_sync_call_method(tmp_ctx, conn, NULL, + (sbus_invoker_writer_fn)_sbus_ifp_invoker_write_s, + bus, path, iface, method, &in, &reply); + if (ret != EOK) { + goto done; + } + + ret = sbus_read_output(out, reply, (sbus_invoker_reader_fn)_sbus_ifp_invoker_read_as, out); + if (ret != EOK) { + goto done; + } + + *_arg0 = talloc_steal(mem_ctx, out->arg0); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +sbus_method_in_s_out_o + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *bus, + const char *path, + const char *iface, + const char *method, + const char * arg0, + const char ** _arg0) +{ + TALLOC_CTX *tmp_ctx; + struct _sbus_ifp_invoker_args_s in; + struct _sbus_ifp_invoker_args_o *out; + DBusMessage *reply; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + out = talloc_zero(tmp_ctx, struct _sbus_ifp_invoker_args_o); + if (out == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to allocate space for output parameters!\n"); + ret = ENOMEM; + goto done; + } + + in.arg0 = arg0; + + ret = sbus_sync_call_method(tmp_ctx, conn, NULL, + (sbus_invoker_writer_fn)_sbus_ifp_invoker_write_s, + bus, path, iface, method, &in, &reply); + if (ret != EOK) { + goto done; + } + + ret = sbus_read_output(out, reply, (sbus_invoker_reader_fn)_sbus_ifp_invoker_read_o, out); + if (ret != EOK) { + goto done; + } + + *_arg0 = talloc_steal(mem_ctx, out->arg0); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +sbus_method_in_s_out_s + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *bus, + const char *path, + const char *iface, + const char *method, + const char * arg0, + const char ** _arg0) +{ + TALLOC_CTX *tmp_ctx; + struct _sbus_ifp_invoker_args_s in; + struct _sbus_ifp_invoker_args_s *out; + DBusMessage *reply; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + out = talloc_zero(tmp_ctx, struct _sbus_ifp_invoker_args_s); + if (out == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to allocate space for output parameters!\n"); + ret = ENOMEM; + goto done; + } + + in.arg0 = arg0; + + ret = sbus_sync_call_method(tmp_ctx, conn, NULL, + (sbus_invoker_writer_fn)_sbus_ifp_invoker_write_s, + bus, path, iface, method, &in, &reply); + if (ret != EOK) { + goto done; + } + + ret = sbus_read_output(out, reply, (sbus_invoker_reader_fn)_sbus_ifp_invoker_read_s, out); + if (ret != EOK) { + goto done; + } + + *_arg0 = talloc_steal(mem_ctx, out->arg0); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +sbus_method_in_sas_out_raw + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *bus, + const char *path, + const char *iface, + const char *method, + const char * arg0, + const char ** arg1, + DBusMessage **_reply) +{ + TALLOC_CTX *tmp_ctx; + struct _sbus_ifp_invoker_args_sas in; + DBusMessage *reply; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + in.arg0 = arg0; + in.arg1 = arg1; + + ret = sbus_sync_call_method(tmp_ctx, conn, NULL, + (sbus_invoker_writer_fn)_sbus_ifp_invoker_write_sas, + bus, path, iface, method, &in, &reply); + if (ret != EOK) { + goto done; + } + + /* Bounded reference cannot be unreferenced with dbus_message_unref. + * For that reason we do not allow NULL memory context as it would + * result in leaking the message memory. */ + if (mem_ctx == NULL) { + ret = EINVAL; + goto done; + } + + ret = sbus_message_bound_steal(mem_ctx, reply); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to steal message [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + *_reply = reply; + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +sbus_method_in_ss_out_o + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *bus, + const char *path, + const char *iface, + const char *method, + const char * arg0, + const char * arg1, + const char ** _arg0) +{ + TALLOC_CTX *tmp_ctx; + struct _sbus_ifp_invoker_args_ss in; + struct _sbus_ifp_invoker_args_o *out; + DBusMessage *reply; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + out = talloc_zero(tmp_ctx, struct _sbus_ifp_invoker_args_o); + if (out == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to allocate space for output parameters!\n"); + ret = ENOMEM; + goto done; + } + + in.arg0 = arg0; + in.arg1 = arg1; + + ret = sbus_sync_call_method(tmp_ctx, conn, NULL, + (sbus_invoker_writer_fn)_sbus_ifp_invoker_write_ss, + bus, path, iface, method, &in, &reply); + if (ret != EOK) { + goto done; + } + + ret = sbus_read_output(out, reply, (sbus_invoker_reader_fn)_sbus_ifp_invoker_read_o, out); + if (ret != EOK) { + goto done; + } + + *_arg0 = talloc_steal(mem_ctx, out->arg0); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +sbus_method_in_ssu_out_ao + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *bus, + const char *path, + const char *iface, + const char *method, + const char * arg0, + const char * arg1, + uint32_t arg2, + const char *** _arg0) +{ + TALLOC_CTX *tmp_ctx; + struct _sbus_ifp_invoker_args_ssu in; + struct _sbus_ifp_invoker_args_ao *out; + DBusMessage *reply; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + out = talloc_zero(tmp_ctx, struct _sbus_ifp_invoker_args_ao); + if (out == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to allocate space for output parameters!\n"); + ret = ENOMEM; + goto done; + } + + in.arg0 = arg0; + in.arg1 = arg1; + in.arg2 = arg2; + + ret = sbus_sync_call_method(tmp_ctx, conn, NULL, + (sbus_invoker_writer_fn)_sbus_ifp_invoker_write_ssu, + bus, path, iface, method, &in, &reply); + if (ret != EOK) { + goto done; + } + + ret = sbus_read_output(out, reply, (sbus_invoker_reader_fn)_sbus_ifp_invoker_read_ao, out); + if (ret != EOK) { + goto done; + } + + *_arg0 = talloc_steal(mem_ctx, out->arg0); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +sbus_method_in_su_out_ao + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *bus, + const char *path, + const char *iface, + const char *method, + const char * arg0, + uint32_t arg1, + const char *** _arg0) +{ + TALLOC_CTX *tmp_ctx; + struct _sbus_ifp_invoker_args_su in; + struct _sbus_ifp_invoker_args_ao *out; + DBusMessage *reply; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + out = talloc_zero(tmp_ctx, struct _sbus_ifp_invoker_args_ao); + if (out == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to allocate space for output parameters!\n"); + ret = ENOMEM; + goto done; + } + + in.arg0 = arg0; + in.arg1 = arg1; + + ret = sbus_sync_call_method(tmp_ctx, conn, NULL, + (sbus_invoker_writer_fn)_sbus_ifp_invoker_write_su, + bus, path, iface, method, &in, &reply); + if (ret != EOK) { + goto done; + } + + ret = sbus_read_output(out, reply, (sbus_invoker_reader_fn)_sbus_ifp_invoker_read_ao, out); + if (ret != EOK) { + goto done; + } + + *_arg0 = talloc_steal(mem_ctx, out->arg0); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +sbus_method_in_u_out_o + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *bus, + const char *path, + const char *iface, + const char *method, + uint32_t arg0, + const char ** _arg0) +{ + TALLOC_CTX *tmp_ctx; + struct _sbus_ifp_invoker_args_u in; + struct _sbus_ifp_invoker_args_o *out; + DBusMessage *reply; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + out = talloc_zero(tmp_ctx, struct _sbus_ifp_invoker_args_o); + if (out == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to allocate space for output parameters!\n"); + ret = ENOMEM; + goto done; + } + + in.arg0 = arg0; + + ret = sbus_sync_call_method(tmp_ctx, conn, NULL, + (sbus_invoker_writer_fn)_sbus_ifp_invoker_write_u, + bus, path, iface, method, &in, &reply); + if (ret != EOK) { + goto done; + } + + ret = sbus_read_output(out, reply, (sbus_invoker_reader_fn)_sbus_ifp_invoker_read_o, out); + if (ret != EOK) { + goto done; + } + + *_arg0 = talloc_steal(mem_ctx, out->arg0); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +sbus_call_ifp_FindBackendByName + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_name, + const char ** _arg_backend) +{ + return sbus_method_in_s_out_o(mem_ctx, conn, + busname, object_path, "org.freedesktop.sssd.infopipe", "FindBackendByName", arg_name, + _arg_backend); +} + +errno_t +sbus_call_ifp_FindDomainByName + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_name, + const char ** _arg_domain) +{ + return sbus_method_in_s_out_o(mem_ctx, conn, + busname, object_path, "org.freedesktop.sssd.infopipe", "FindDomainByName", arg_name, + _arg_domain); +} + +errno_t +sbus_call_ifp_FindMonitor + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char ** _arg_monitor) +{ + return sbus_method_in__out_o(mem_ctx, conn, + busname, object_path, "org.freedesktop.sssd.infopipe", "FindMonitor", + _arg_monitor); +} + +errno_t +sbus_call_ifp_FindResponderByName + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_name, + const char ** _arg_responder) +{ + return sbus_method_in_s_out_o(mem_ctx, conn, + busname, object_path, "org.freedesktop.sssd.infopipe", "FindResponderByName", arg_name, + _arg_responder); +} + +errno_t +sbus_call_ifp_GetUserAttr + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_user, + const char ** arg_attr, + DBusMessage **_reply) +{ + return sbus_method_in_sas_out_raw(mem_ctx, conn, + busname, object_path, "org.freedesktop.sssd.infopipe", "GetUserAttr", arg_user, arg_attr, + _reply); +} + +errno_t +sbus_call_ifp_GetUserGroups + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_user, + const char *** _arg_values) +{ + return sbus_method_in_s_out_as(mem_ctx, conn, + busname, object_path, "org.freedesktop.sssd.infopipe", "GetUserGroups", arg_user, + _arg_values); +} + +errno_t +sbus_call_ifp_ListBackends + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char *** _arg_backends) +{ + return sbus_method_in__out_ao(mem_ctx, conn, + busname, object_path, "org.freedesktop.sssd.infopipe", "ListBackends", + _arg_backends); +} + +errno_t +sbus_call_ifp_ListComponents + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char *** _arg_components) +{ + return sbus_method_in__out_ao(mem_ctx, conn, + busname, object_path, "org.freedesktop.sssd.infopipe", "ListComponents", + _arg_components); +} + +errno_t +sbus_call_ifp_ListDomains + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char *** _arg_domain) +{ + return sbus_method_in__out_ao(mem_ctx, conn, + busname, object_path, "org.freedesktop.sssd.infopipe", "ListDomains", + _arg_domain); +} + +errno_t +sbus_call_ifp_ListResponders + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char *** _arg_responders) +{ + return sbus_method_in__out_ao(mem_ctx, conn, + busname, object_path, "org.freedesktop.sssd.infopipe", "ListResponders", + _arg_responders); +} + +errno_t +sbus_call_ifp_Ping + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_ping, + const char ** _arg_pong) +{ + return sbus_method_in_s_out_s(mem_ctx, conn, + busname, object_path, "org.freedesktop.sssd.infopipe", "Ping", arg_ping, + _arg_pong); +} + +errno_t +sbus_call_ifp_cache_List + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char *** _arg_result) +{ + return sbus_method_in__out_ao(mem_ctx, conn, + busname, object_path, "org.freedesktop.sssd.infopipe.Cache", "List", + _arg_result); +} + +errno_t +sbus_call_ifp_cache_ListByDomain + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_domain_name, + const char *** _arg_result) +{ + return sbus_method_in_s_out_ao(mem_ctx, conn, + busname, object_path, "org.freedesktop.sssd.infopipe.Cache", "ListByDomain", arg_domain_name, + _arg_result); +} + +errno_t +sbus_call_ifp_cache_object_Remove + (struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + bool* _arg_result) +{ + return sbus_method_in__out_b(conn, + busname, object_path, "org.freedesktop.sssd.infopipe.Cache.Object", "Remove", + _arg_result); +} + +errno_t +sbus_call_ifp_cache_object_Store + (struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + bool* _arg_result) +{ + return sbus_method_in__out_b(conn, + busname, object_path, "org.freedesktop.sssd.infopipe.Cache.Object", "Store", + _arg_result); +} + +errno_t +sbus_call_ifp_domain_ActiveServer + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_service, + const char ** _arg_server) +{ + return sbus_method_in_s_out_s(mem_ctx, conn, + busname, object_path, "org.freedesktop.sssd.infopipe.Domains.Domain", "ActiveServer", arg_service, + _arg_server); +} + +errno_t +sbus_call_ifp_domain_IsOnline + (struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + bool* _arg_status) +{ + return sbus_method_in__out_b(conn, + busname, object_path, "org.freedesktop.sssd.infopipe.Domains.Domain", "IsOnline", + _arg_status); +} + +errno_t +sbus_call_ifp_domain_ListServers + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_service_name, + const char *** _arg_servers) +{ + return sbus_method_in_s_out_as(mem_ctx, conn, + busname, object_path, "org.freedesktop.sssd.infopipe.Domains.Domain", "ListServers", arg_service_name, + _arg_servers); +} + +errno_t +sbus_call_ifp_domain_ListServices + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char *** _arg_services) +{ + return sbus_method_in__out_as(mem_ctx, conn, + busname, object_path, "org.freedesktop.sssd.infopipe.Domains.Domain", "ListServices", + _arg_services); +} + +errno_t +sbus_call_ifp_domain_RefreshAccessRules + (struct sbus_sync_connection *conn, + const char *busname, + const char *object_path) +{ + return sbus_method_in__out_(conn, + busname, object_path, "org.freedesktop.sssd.infopipe.Domains.Domain", "RefreshAccessRules"); +} + +errno_t +sbus_call_ifp_groups_FindByID + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + uint32_t arg_id, + const char ** _arg_result) +{ + return sbus_method_in_u_out_o(mem_ctx, conn, + busname, object_path, "org.freedesktop.sssd.infopipe.Groups", "FindByID", arg_id, + _arg_result); +} + +errno_t +sbus_call_ifp_groups_FindByName + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_name, + const char ** _arg_result) +{ + return sbus_method_in_s_out_o(mem_ctx, conn, + busname, object_path, "org.freedesktop.sssd.infopipe.Groups", "FindByName", arg_name, + _arg_result); +} + +errno_t +sbus_call_ifp_groups_ListByDomainAndName + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_domain_name, + const char * arg_name_filter, + uint32_t arg_limit, + const char *** _arg_result) +{ + return sbus_method_in_ssu_out_ao(mem_ctx, conn, + busname, object_path, "org.freedesktop.sssd.infopipe.Groups", "ListByDomainAndName", arg_domain_name, arg_name_filter, arg_limit, + _arg_result); +} + +errno_t +sbus_call_ifp_groups_ListByName + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_name_filter, + uint32_t arg_limit, + const char *** _arg_result) +{ + return sbus_method_in_su_out_ao(mem_ctx, conn, + busname, object_path, "org.freedesktop.sssd.infopipe.Groups", "ListByName", arg_name_filter, arg_limit, + _arg_result); +} + +errno_t +sbus_call_ifp_group_UpdateMemberList + (struct sbus_sync_connection *conn, + const char *busname, + const char *object_path) +{ + return sbus_method_in__out_(conn, + busname, object_path, "org.freedesktop.sssd.infopipe.Groups.Group", "UpdateMemberList"); +} + +errno_t +sbus_call_ifp_users_FindByCertificate + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_pem_cert, + const char ** _arg_result) +{ + return sbus_method_in_s_out_o(mem_ctx, conn, + busname, object_path, "org.freedesktop.sssd.infopipe.Users", "FindByCertificate", arg_pem_cert, + _arg_result); +} + +errno_t +sbus_call_ifp_users_FindByID + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + uint32_t arg_id, + const char ** _arg_result) +{ + return sbus_method_in_u_out_o(mem_ctx, conn, + busname, object_path, "org.freedesktop.sssd.infopipe.Users", "FindByID", arg_id, + _arg_result); +} + +errno_t +sbus_call_ifp_users_FindByName + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_name, + const char ** _arg_result) +{ + return sbus_method_in_s_out_o(mem_ctx, conn, + busname, object_path, "org.freedesktop.sssd.infopipe.Users", "FindByName", arg_name, + _arg_result); +} + +errno_t +sbus_call_ifp_users_FindByNameAndCertificate + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_name, + const char * arg_pem_cert, + const char ** _arg_result) +{ + return sbus_method_in_ss_out_o(mem_ctx, conn, + busname, object_path, "org.freedesktop.sssd.infopipe.Users", "FindByNameAndCertificate", arg_name, arg_pem_cert, + _arg_result); +} + +errno_t +sbus_call_ifp_users_FindByValidCertificate + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_pem_cert, + const char ** _arg_result) +{ + return sbus_method_in_s_out_o(mem_ctx, conn, + busname, object_path, "org.freedesktop.sssd.infopipe.Users", "FindByValidCertificate", arg_pem_cert, + _arg_result); +} + +errno_t +sbus_call_ifp_users_ListByAttr + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_attribute, + const char * arg_attr_filter, + uint32_t arg_limit, + const char *** _arg_result) +{ + return sbus_method_in_ssu_out_ao(mem_ctx, conn, + busname, object_path, "org.freedesktop.sssd.infopipe.Users", "ListByAttr", arg_attribute, arg_attr_filter, arg_limit, + _arg_result); +} + +errno_t +sbus_call_ifp_users_ListByCertificate + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_pem_cert, + uint32_t arg_limit, + const char *** _arg_result) +{ + return sbus_method_in_su_out_ao(mem_ctx, conn, + busname, object_path, "org.freedesktop.sssd.infopipe.Users", "ListByCertificate", arg_pem_cert, arg_limit, + _arg_result); +} + +errno_t +sbus_call_ifp_users_ListByDomainAndName + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_domain_name, + const char * arg_name_filter, + uint32_t arg_limit, + const char *** _arg_result) +{ + return sbus_method_in_ssu_out_ao(mem_ctx, conn, + busname, object_path, "org.freedesktop.sssd.infopipe.Users", "ListByDomainAndName", arg_domain_name, arg_name_filter, arg_limit, + _arg_result); +} + +errno_t +sbus_call_ifp_users_ListByName + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_name_filter, + uint32_t arg_limit, + const char *** _arg_result) +{ + return sbus_method_in_su_out_ao(mem_ctx, conn, + busname, object_path, "org.freedesktop.sssd.infopipe.Users", "ListByName", arg_name_filter, arg_limit, + _arg_result); +} + +errno_t +sbus_call_ifp_user_UpdateGroupsList + (struct sbus_sync_connection *conn, + const char *busname, + const char *object_path) +{ + return sbus_method_in__out_(conn, + busname, object_path, "org.freedesktop.sssd.infopipe.Users.User", "UpdateGroupsList"); +} + +static errno_t +sbus_get_ao + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *bus, + const char *path, + const char *iface, + const char *property, + const char *** _value) +{ + TALLOC_CTX *tmp_ctx; + struct _sbus_ifp_invoker_args_ao *out; + sbus_value_reader_fn reader; + sbus_value_reader_talloc_fn reader_talloc; + DBusMessage *reply; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + out = talloc_zero(tmp_ctx, struct _sbus_ifp_invoker_args_ao); + if (out == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to allocate space for output parameters!\n"); + ret = ENOMEM; + goto done; + } + + ret = sbus_call_DBusProperties_Get(tmp_ctx, conn, + bus, path, iface, property, &reply); + if (ret != EOK) { + goto done; + } + + reader = NULL; + reader_talloc = (sbus_value_reader_talloc_fn)sbus_iterator_read_ao; + ret = sbus_parse_get_message(out, reader, reader_talloc, reply, &out->arg0); + if (ret != EOK) { + goto done; + } + + *_value = talloc_steal(mem_ctx, out->arg0); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +sbus_get_as + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *bus, + const char *path, + const char *iface, + const char *property, + const char *** _value) +{ + TALLOC_CTX *tmp_ctx; + struct _sbus_ifp_invoker_args_as *out; + sbus_value_reader_fn reader; + sbus_value_reader_talloc_fn reader_talloc; + DBusMessage *reply; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + out = talloc_zero(tmp_ctx, struct _sbus_ifp_invoker_args_as); + if (out == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to allocate space for output parameters!\n"); + ret = ENOMEM; + goto done; + } + + ret = sbus_call_DBusProperties_Get(tmp_ctx, conn, + bus, path, iface, property, &reply); + if (ret != EOK) { + goto done; + } + + reader = NULL; + reader_talloc = (sbus_value_reader_talloc_fn)sbus_iterator_read_as; + ret = sbus_parse_get_message(out, reader, reader_talloc, reply, &out->arg0); + if (ret != EOK) { + goto done; + } + + *_value = talloc_steal(mem_ctx, out->arg0); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +sbus_get_b + (struct sbus_sync_connection *conn, + const char *bus, + const char *path, + const char *iface, + const char *property, + bool* _value) +{ + TALLOC_CTX *tmp_ctx; + struct _sbus_ifp_invoker_args_b *out; + sbus_value_reader_fn reader; + sbus_value_reader_talloc_fn reader_talloc; + DBusMessage *reply; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + out = talloc_zero(tmp_ctx, struct _sbus_ifp_invoker_args_b); + if (out == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to allocate space for output parameters!\n"); + ret = ENOMEM; + goto done; + } + + ret = sbus_call_DBusProperties_Get(tmp_ctx, conn, + bus, path, iface, property, &reply); + if (ret != EOK) { + goto done; + } + + reader = (sbus_value_reader_fn)sbus_iterator_read_b; + reader_talloc = NULL; + ret = sbus_parse_get_message(out, reader, reader_talloc, reply, &out->arg0); + if (ret != EOK) { + goto done; + } + + *_value = out->arg0; + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +sbus_get_ifp_extra + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *bus, + const char *path, + const char *iface, + const char *property, + hash_table_t ** _value) +{ + TALLOC_CTX *tmp_ctx; + struct _sbus_ifp_invoker_args_ifp_extra *out; + sbus_value_reader_fn reader; + sbus_value_reader_talloc_fn reader_talloc; + DBusMessage *reply; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + out = talloc_zero(tmp_ctx, struct _sbus_ifp_invoker_args_ifp_extra); + if (out == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to allocate space for output parameters!\n"); + ret = ENOMEM; + goto done; + } + + ret = sbus_call_DBusProperties_Get(tmp_ctx, conn, + bus, path, iface, property, &reply); + if (ret != EOK) { + goto done; + } + + reader = NULL; + reader_talloc = (sbus_value_reader_talloc_fn)sbus_iterator_read_ifp_extra; + ret = sbus_parse_get_message(out, reader, reader_talloc, reply, &out->arg0); + if (ret != EOK) { + goto done; + } + + *_value = talloc_steal(mem_ctx, out->arg0); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +sbus_get_o + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *bus, + const char *path, + const char *iface, + const char *property, + const char ** _value) +{ + TALLOC_CTX *tmp_ctx; + struct _sbus_ifp_invoker_args_o *out; + sbus_value_reader_fn reader; + sbus_value_reader_talloc_fn reader_talloc; + DBusMessage *reply; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + out = talloc_zero(tmp_ctx, struct _sbus_ifp_invoker_args_o); + if (out == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to allocate space for output parameters!\n"); + ret = ENOMEM; + goto done; + } + + ret = sbus_call_DBusProperties_Get(tmp_ctx, conn, + bus, path, iface, property, &reply); + if (ret != EOK) { + goto done; + } + + reader = NULL; + reader_talloc = (sbus_value_reader_talloc_fn)sbus_iterator_read_o; + ret = sbus_parse_get_message(out, reader, reader_talloc, reply, &out->arg0); + if (ret != EOK) { + goto done; + } + + *_value = talloc_steal(mem_ctx, out->arg0); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +sbus_get_s + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *bus, + const char *path, + const char *iface, + const char *property, + const char ** _value) +{ + TALLOC_CTX *tmp_ctx; + struct _sbus_ifp_invoker_args_s *out; + sbus_value_reader_fn reader; + sbus_value_reader_talloc_fn reader_talloc; + DBusMessage *reply; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + out = talloc_zero(tmp_ctx, struct _sbus_ifp_invoker_args_s); + if (out == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to allocate space for output parameters!\n"); + ret = ENOMEM; + goto done; + } + + ret = sbus_call_DBusProperties_Get(tmp_ctx, conn, + bus, path, iface, property, &reply); + if (ret != EOK) { + goto done; + } + + reader = NULL; + reader_talloc = (sbus_value_reader_talloc_fn)sbus_iterator_read_s; + ret = sbus_parse_get_message(out, reader, reader_talloc, reply, &out->arg0); + if (ret != EOK) { + goto done; + } + + *_value = talloc_steal(mem_ctx, out->arg0); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +sbus_get_u + (struct sbus_sync_connection *conn, + const char *bus, + const char *path, + const char *iface, + const char *property, + uint32_t* _value) +{ + TALLOC_CTX *tmp_ctx; + struct _sbus_ifp_invoker_args_u *out; + sbus_value_reader_fn reader; + sbus_value_reader_talloc_fn reader_talloc; + DBusMessage *reply; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + out = talloc_zero(tmp_ctx, struct _sbus_ifp_invoker_args_u); + if (out == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to allocate space for output parameters!\n"); + ret = ENOMEM; + goto done; + } + + ret = sbus_call_DBusProperties_Get(tmp_ctx, conn, + bus, path, iface, property, &reply); + if (ret != EOK) { + goto done; + } + + reader = (sbus_value_reader_fn)sbus_iterator_read_u; + reader_talloc = NULL; + ret = sbus_parse_get_message(out, reader, reader_talloc, reply, &out->arg0); + if (ret != EOK) { + goto done; + } + + *_value = out->arg0; + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +sbus_get_ifp_components_debug_level + (struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + uint32_t* _value) +{ + return sbus_get_u(conn, busname, object_path, + "org.freedesktop.sssd.infopipe.Components", "debug_level", _value); +} + +errno_t +sbus_get_ifp_components_enabled + (struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + bool* _value) +{ + return sbus_get_b(conn, busname, object_path, + "org.freedesktop.sssd.infopipe.Components", "enabled", _value); +} + +errno_t +sbus_get_ifp_components_name + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char ** _value) +{ + return sbus_get_s(mem_ctx, conn, busname, object_path, + "org.freedesktop.sssd.infopipe.Components", "name", _value); +} + +errno_t +sbus_get_ifp_components_providers + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char *** _value) +{ + return sbus_get_as(mem_ctx, conn, busname, object_path, + "org.freedesktop.sssd.infopipe.Components", "providers", _value); +} + +errno_t +sbus_get_ifp_components_type + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char ** _value) +{ + return sbus_get_s(mem_ctx, conn, busname, object_path, + "org.freedesktop.sssd.infopipe.Components", "type", _value); +} + +errno_t +sbus_get_ifp_domains_backup_servers + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char *** _value) +{ + return sbus_get_as(mem_ctx, conn, busname, object_path, + "org.freedesktop.sssd.infopipe.Domains", "backup_servers", _value); +} + +errno_t +sbus_get_ifp_domains_enumerable + (struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + bool* _value) +{ + return sbus_get_b(conn, busname, object_path, + "org.freedesktop.sssd.infopipe.Domains", "enumerable", _value); +} + +errno_t +sbus_get_ifp_domains_forest + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char ** _value) +{ + return sbus_get_s(mem_ctx, conn, busname, object_path, + "org.freedesktop.sssd.infopipe.Domains", "forest", _value); +} + +errno_t +sbus_get_ifp_domains_fully_qualified_name_format + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char ** _value) +{ + return sbus_get_s(mem_ctx, conn, busname, object_path, + "org.freedesktop.sssd.infopipe.Domains", "fully_qualified_name_format", _value); +} + +errno_t +sbus_get_ifp_domains_login_format + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char ** _value) +{ + return sbus_get_s(mem_ctx, conn, busname, object_path, + "org.freedesktop.sssd.infopipe.Domains", "login_format", _value); +} + +errno_t +sbus_get_ifp_domains_max_id + (struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + uint32_t* _value) +{ + return sbus_get_u(conn, busname, object_path, + "org.freedesktop.sssd.infopipe.Domains", "max_id", _value); +} + +errno_t +sbus_get_ifp_domains_min_id + (struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + uint32_t* _value) +{ + return sbus_get_u(conn, busname, object_path, + "org.freedesktop.sssd.infopipe.Domains", "min_id", _value); +} + +errno_t +sbus_get_ifp_domains_name + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char ** _value) +{ + return sbus_get_s(mem_ctx, conn, busname, object_path, + "org.freedesktop.sssd.infopipe.Domains", "name", _value); +} + +errno_t +sbus_get_ifp_domains_parent_domain + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char ** _value) +{ + return sbus_get_o(mem_ctx, conn, busname, object_path, + "org.freedesktop.sssd.infopipe.Domains", "parent_domain", _value); +} + +errno_t +sbus_get_ifp_domains_primary_servers + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char *** _value) +{ + return sbus_get_as(mem_ctx, conn, busname, object_path, + "org.freedesktop.sssd.infopipe.Domains", "primary_servers", _value); +} + +errno_t +sbus_get_ifp_domains_provider + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char ** _value) +{ + return sbus_get_s(mem_ctx, conn, busname, object_path, + "org.freedesktop.sssd.infopipe.Domains", "provider", _value); +} + +errno_t +sbus_get_ifp_domains_realm + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char ** _value) +{ + return sbus_get_s(mem_ctx, conn, busname, object_path, + "org.freedesktop.sssd.infopipe.Domains", "realm", _value); +} + +errno_t +sbus_get_ifp_domains_subdomain + (struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + bool* _value) +{ + return sbus_get_b(conn, busname, object_path, + "org.freedesktop.sssd.infopipe.Domains", "subdomain", _value); +} + +errno_t +sbus_get_ifp_domains_use_fully_qualified_names + (struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + bool* _value) +{ + return sbus_get_b(conn, busname, object_path, + "org.freedesktop.sssd.infopipe.Domains", "use_fully_qualified_names", _value); +} + +errno_t +sbus_get_ifp_group_gidNumber + (struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + uint32_t* _value) +{ + return sbus_get_u(conn, busname, object_path, + "org.freedesktop.sssd.infopipe.Groups.Group", "gidNumber", _value); +} + +errno_t +sbus_get_ifp_group_groups + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char *** _value) +{ + return sbus_get_ao(mem_ctx, conn, busname, object_path, + "org.freedesktop.sssd.infopipe.Groups.Group", "groups", _value); +} + +errno_t +sbus_get_ifp_group_name + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char ** _value) +{ + return sbus_get_s(mem_ctx, conn, busname, object_path, + "org.freedesktop.sssd.infopipe.Groups.Group", "name", _value); +} + +errno_t +sbus_get_ifp_group_uniqueID + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char ** _value) +{ + return sbus_get_s(mem_ctx, conn, busname, object_path, + "org.freedesktop.sssd.infopipe.Groups.Group", "uniqueID", _value); +} + +errno_t +sbus_get_ifp_group_users + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char *** _value) +{ + return sbus_get_ao(mem_ctx, conn, busname, object_path, + "org.freedesktop.sssd.infopipe.Groups.Group", "users", _value); +} + +errno_t +sbus_get_ifp_user_domain + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char ** _value) +{ + return sbus_get_o(mem_ctx, conn, busname, object_path, + "org.freedesktop.sssd.infopipe.Users.User", "domain", _value); +} + +errno_t +sbus_get_ifp_user_domainname + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char ** _value) +{ + return sbus_get_s(mem_ctx, conn, busname, object_path, + "org.freedesktop.sssd.infopipe.Users.User", "domainname", _value); +} + +errno_t +sbus_get_ifp_user_extraAttributes + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + hash_table_t ** _value) +{ + return sbus_get_ifp_extra(mem_ctx, conn, busname, object_path, + "org.freedesktop.sssd.infopipe.Users.User", "extraAttributes", _value); +} + +errno_t +sbus_get_ifp_user_gecos + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char ** _value) +{ + return sbus_get_s(mem_ctx, conn, busname, object_path, + "org.freedesktop.sssd.infopipe.Users.User", "gecos", _value); +} + +errno_t +sbus_get_ifp_user_gidNumber + (struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + uint32_t* _value) +{ + return sbus_get_u(conn, busname, object_path, + "org.freedesktop.sssd.infopipe.Users.User", "gidNumber", _value); +} + +errno_t +sbus_get_ifp_user_groups + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char *** _value) +{ + return sbus_get_ao(mem_ctx, conn, busname, object_path, + "org.freedesktop.sssd.infopipe.Users.User", "groups", _value); +} + +errno_t +sbus_get_ifp_user_homeDirectory + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char ** _value) +{ + return sbus_get_s(mem_ctx, conn, busname, object_path, + "org.freedesktop.sssd.infopipe.Users.User", "homeDirectory", _value); +} + +errno_t +sbus_get_ifp_user_loginShell + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char ** _value) +{ + return sbus_get_s(mem_ctx, conn, busname, object_path, + "org.freedesktop.sssd.infopipe.Users.User", "loginShell", _value); +} + +errno_t +sbus_get_ifp_user_name + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char ** _value) +{ + return sbus_get_s(mem_ctx, conn, busname, object_path, + "org.freedesktop.sssd.infopipe.Users.User", "name", _value); +} + +errno_t +sbus_get_ifp_user_uidNumber + (struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + uint32_t* _value) +{ + return sbus_get_u(conn, busname, object_path, + "org.freedesktop.sssd.infopipe.Users.User", "uidNumber", _value); +} + +errno_t +sbus_get_ifp_user_uniqueID + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char ** _value) +{ + return sbus_get_s(mem_ctx, conn, busname, object_path, + "org.freedesktop.sssd.infopipe.Users.User", "uniqueID", _value); +} + +errno_t +sbus_getall_ifp_components + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + struct sbus_all_ifp_components **_properties) +{ + TALLOC_CTX *tmp_ctx; + struct sbus_all_ifp_components *properties; + DBusMessage *reply; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + properties = talloc_zero(tmp_ctx, struct sbus_all_ifp_components); + if (properties == NULL) { + ret = ENOMEM; + goto done; + } + + struct sbus_parse_getall_table table[] = { + {"debug_level", (sbus_value_reader_fn)sbus_iterator_read_u, NULL, + &properties->debug_level.value, &properties->debug_level.is_set}, + {"enabled", (sbus_value_reader_fn)sbus_iterator_read_b, NULL, + &properties->enabled.value, &properties->enabled.is_set}, + {"name", NULL, (sbus_value_reader_talloc_fn)sbus_iterator_read_s, + &properties->name.value, &properties->name.is_set}, + {"providers", NULL, (sbus_value_reader_talloc_fn)sbus_iterator_read_as, + &properties->providers.value, &properties->providers.is_set}, + {"type", NULL, (sbus_value_reader_talloc_fn)sbus_iterator_read_s, + &properties->type.value, &properties->type.is_set}, + {NULL, NULL, NULL, NULL, NULL} + }; + + ret = sbus_call_DBusProperties_GetAll(tmp_ctx, conn, + busname, object_path, "org.freedesktop.sssd.infopipe.Components", &reply); + if (ret != EOK) { + goto done; + } + + ret = sbus_parse_getall_message(properties, table, reply); + if (ret != EOK) { + goto done; + } + + *_properties = talloc_steal(mem_ctx, properties); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +sbus_getall_ifp_domains + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + struct sbus_all_ifp_domains **_properties) +{ + TALLOC_CTX *tmp_ctx; + struct sbus_all_ifp_domains *properties; + DBusMessage *reply; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + properties = talloc_zero(tmp_ctx, struct sbus_all_ifp_domains); + if (properties == NULL) { + ret = ENOMEM; + goto done; + } + + struct sbus_parse_getall_table table[] = { + {"enumerable", (sbus_value_reader_fn)sbus_iterator_read_b, NULL, + &properties->enumerable.value, &properties->enumerable.is_set}, + {"max_id", (sbus_value_reader_fn)sbus_iterator_read_u, NULL, + &properties->max_id.value, &properties->max_id.is_set}, + {"min_id", (sbus_value_reader_fn)sbus_iterator_read_u, NULL, + &properties->min_id.value, &properties->min_id.is_set}, + {"subdomain", (sbus_value_reader_fn)sbus_iterator_read_b, NULL, + &properties->subdomain.value, &properties->subdomain.is_set}, + {"use_fully_qualified_names", (sbus_value_reader_fn)sbus_iterator_read_b, NULL, + &properties->use_fully_qualified_names.value, &properties->use_fully_qualified_names.is_set}, + {"backup_servers", NULL, (sbus_value_reader_talloc_fn)sbus_iterator_read_as, + &properties->backup_servers.value, &properties->backup_servers.is_set}, + {"forest", NULL, (sbus_value_reader_talloc_fn)sbus_iterator_read_s, + &properties->forest.value, &properties->forest.is_set}, + {"fully_qualified_name_format", NULL, (sbus_value_reader_talloc_fn)sbus_iterator_read_s, + &properties->fully_qualified_name_format.value, &properties->fully_qualified_name_format.is_set}, + {"login_format", NULL, (sbus_value_reader_talloc_fn)sbus_iterator_read_s, + &properties->login_format.value, &properties->login_format.is_set}, + {"name", NULL, (sbus_value_reader_talloc_fn)sbus_iterator_read_s, + &properties->name.value, &properties->name.is_set}, + {"parent_domain", NULL, (sbus_value_reader_talloc_fn)sbus_iterator_read_o, + &properties->parent_domain.value, &properties->parent_domain.is_set}, + {"primary_servers", NULL, (sbus_value_reader_talloc_fn)sbus_iterator_read_as, + &properties->primary_servers.value, &properties->primary_servers.is_set}, + {"provider", NULL, (sbus_value_reader_talloc_fn)sbus_iterator_read_s, + &properties->provider.value, &properties->provider.is_set}, + {"realm", NULL, (sbus_value_reader_talloc_fn)sbus_iterator_read_s, + &properties->realm.value, &properties->realm.is_set}, + {NULL, NULL, NULL, NULL, NULL} + }; + + ret = sbus_call_DBusProperties_GetAll(tmp_ctx, conn, + busname, object_path, "org.freedesktop.sssd.infopipe.Domains", &reply); + if (ret != EOK) { + goto done; + } + + ret = sbus_parse_getall_message(properties, table, reply); + if (ret != EOK) { + goto done; + } + + *_properties = talloc_steal(mem_ctx, properties); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +sbus_getall_ifp_group + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + struct sbus_all_ifp_group **_properties) +{ + TALLOC_CTX *tmp_ctx; + struct sbus_all_ifp_group *properties; + DBusMessage *reply; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + properties = talloc_zero(tmp_ctx, struct sbus_all_ifp_group); + if (properties == NULL) { + ret = ENOMEM; + goto done; + } + + struct sbus_parse_getall_table table[] = { + {"gidNumber", (sbus_value_reader_fn)sbus_iterator_read_u, NULL, + &properties->gidNumber.value, &properties->gidNumber.is_set}, + {"groups", NULL, (sbus_value_reader_talloc_fn)sbus_iterator_read_ao, + &properties->groups.value, &properties->groups.is_set}, + {"name", NULL, (sbus_value_reader_talloc_fn)sbus_iterator_read_s, + &properties->name.value, &properties->name.is_set}, + {"uniqueID", NULL, (sbus_value_reader_talloc_fn)sbus_iterator_read_s, + &properties->uniqueID.value, &properties->uniqueID.is_set}, + {"users", NULL, (sbus_value_reader_talloc_fn)sbus_iterator_read_ao, + &properties->users.value, &properties->users.is_set}, + {NULL, NULL, NULL, NULL, NULL} + }; + + ret = sbus_call_DBusProperties_GetAll(tmp_ctx, conn, + busname, object_path, "org.freedesktop.sssd.infopipe.Groups.Group", &reply); + if (ret != EOK) { + goto done; + } + + ret = sbus_parse_getall_message(properties, table, reply); + if (ret != EOK) { + goto done; + } + + *_properties = talloc_steal(mem_ctx, properties); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +sbus_getall_ifp_user + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + struct sbus_all_ifp_user **_properties) +{ + TALLOC_CTX *tmp_ctx; + struct sbus_all_ifp_user *properties; + DBusMessage *reply; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + properties = talloc_zero(tmp_ctx, struct sbus_all_ifp_user); + if (properties == NULL) { + ret = ENOMEM; + goto done; + } + + struct sbus_parse_getall_table table[] = { + {"gidNumber", (sbus_value_reader_fn)sbus_iterator_read_u, NULL, + &properties->gidNumber.value, &properties->gidNumber.is_set}, + {"uidNumber", (sbus_value_reader_fn)sbus_iterator_read_u, NULL, + &properties->uidNumber.value, &properties->uidNumber.is_set}, + {"domain", NULL, (sbus_value_reader_talloc_fn)sbus_iterator_read_o, + &properties->domain.value, &properties->domain.is_set}, + {"domainname", NULL, (sbus_value_reader_talloc_fn)sbus_iterator_read_s, + &properties->domainname.value, &properties->domainname.is_set}, + {"extraAttributes", NULL, (sbus_value_reader_talloc_fn)sbus_iterator_read_ifp_extra, + &properties->extraAttributes.value, &properties->extraAttributes.is_set}, + {"gecos", NULL, (sbus_value_reader_talloc_fn)sbus_iterator_read_s, + &properties->gecos.value, &properties->gecos.is_set}, + {"groups", NULL, (sbus_value_reader_talloc_fn)sbus_iterator_read_ao, + &properties->groups.value, &properties->groups.is_set}, + {"homeDirectory", NULL, (sbus_value_reader_talloc_fn)sbus_iterator_read_s, + &properties->homeDirectory.value, &properties->homeDirectory.is_set}, + {"loginShell", NULL, (sbus_value_reader_talloc_fn)sbus_iterator_read_s, + &properties->loginShell.value, &properties->loginShell.is_set}, + {"name", NULL, (sbus_value_reader_talloc_fn)sbus_iterator_read_s, + &properties->name.value, &properties->name.is_set}, + {"uniqueID", NULL, (sbus_value_reader_talloc_fn)sbus_iterator_read_s, + &properties->uniqueID.value, &properties->uniqueID.is_set}, + {NULL, NULL, NULL, NULL, NULL} + }; + + ret = sbus_call_DBusProperties_GetAll(tmp_ctx, conn, + busname, object_path, "org.freedesktop.sssd.infopipe.Users.User", &reply); + if (ret != EOK) { + goto done; + } + + ret = sbus_parse_getall_message(properties, table, reply); + if (ret != EOK) { + goto done; + } + + *_properties = talloc_steal(mem_ctx, properties); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} diff --git a/src/responder/ifp/ifp_iface/sbus_ifp_client_sync.h b/src/responder/ifp/ifp_iface/sbus_ifp_client_sync.h new file mode 100644 index 0000000..b5455e9 --- /dev/null +++ b/src/responder/ifp/ifp_iface/sbus_ifp_client_sync.h @@ -0,0 +1,637 @@ +/* + Generated by sbus code generator + + Copyright (C) 2017 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/>. +*/ + +#ifndef _SBUS_IFP_CLIENT_SYNC_H_ +#define _SBUS_IFP_CLIENT_SYNC_H_ + +#include <errno.h> +#include <talloc.h> +#include <tevent.h> + +#include "sbus/sbus_sync.h" +#include "responder/ifp/ifp_iface/sbus_ifp_client_properties.h" +#include "responder/ifp/ifp_iface/ifp_iface_types.h" + +errno_t +sbus_call_ifp_FindBackendByName + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_name, + const char ** _arg_backend); + +errno_t +sbus_call_ifp_FindDomainByName + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_name, + const char ** _arg_domain); + +errno_t +sbus_call_ifp_FindMonitor + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char ** _arg_monitor); + +errno_t +sbus_call_ifp_FindResponderByName + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_name, + const char ** _arg_responder); + +errno_t +sbus_call_ifp_GetUserAttr + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_user, + const char ** arg_attr, + DBusMessage **_reply); + +errno_t +sbus_call_ifp_GetUserGroups + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_user, + const char *** _arg_values); + +errno_t +sbus_call_ifp_ListBackends + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char *** _arg_backends); + +errno_t +sbus_call_ifp_ListComponents + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char *** _arg_components); + +errno_t +sbus_call_ifp_ListDomains + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char *** _arg_domain); + +errno_t +sbus_call_ifp_ListResponders + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char *** _arg_responders); + +errno_t +sbus_call_ifp_Ping + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_ping, + const char ** _arg_pong); + +errno_t +sbus_call_ifp_cache_List + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char *** _arg_result); + +errno_t +sbus_call_ifp_cache_ListByDomain + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_domain_name, + const char *** _arg_result); + +errno_t +sbus_call_ifp_cache_object_Remove + (struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + bool* _arg_result); + +errno_t +sbus_call_ifp_cache_object_Store + (struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + bool* _arg_result); + +errno_t +sbus_call_ifp_domain_ActiveServer + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_service, + const char ** _arg_server); + +errno_t +sbus_call_ifp_domain_IsOnline + (struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + bool* _arg_status); + +errno_t +sbus_call_ifp_domain_ListServers + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_service_name, + const char *** _arg_servers); + +errno_t +sbus_call_ifp_domain_ListServices + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char *** _arg_services); + +errno_t +sbus_call_ifp_domain_RefreshAccessRules + (struct sbus_sync_connection *conn, + const char *busname, + const char *object_path); + +errno_t +sbus_call_ifp_groups_FindByID + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + uint32_t arg_id, + const char ** _arg_result); + +errno_t +sbus_call_ifp_groups_FindByName + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_name, + const char ** _arg_result); + +errno_t +sbus_call_ifp_groups_ListByDomainAndName + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_domain_name, + const char * arg_name_filter, + uint32_t arg_limit, + const char *** _arg_result); + +errno_t +sbus_call_ifp_groups_ListByName + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_name_filter, + uint32_t arg_limit, + const char *** _arg_result); + +errno_t +sbus_call_ifp_group_UpdateMemberList + (struct sbus_sync_connection *conn, + const char *busname, + const char *object_path); + +errno_t +sbus_call_ifp_users_FindByCertificate + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_pem_cert, + const char ** _arg_result); + +errno_t +sbus_call_ifp_users_FindByID + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + uint32_t arg_id, + const char ** _arg_result); + +errno_t +sbus_call_ifp_users_FindByName + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_name, + const char ** _arg_result); + +errno_t +sbus_call_ifp_users_FindByNameAndCertificate + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_name, + const char * arg_pem_cert, + const char ** _arg_result); + +errno_t +sbus_call_ifp_users_FindByValidCertificate + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_pem_cert, + const char ** _arg_result); + +errno_t +sbus_call_ifp_users_ListByAttr + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_attribute, + const char * arg_attr_filter, + uint32_t arg_limit, + const char *** _arg_result); + +errno_t +sbus_call_ifp_users_ListByCertificate + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_pem_cert, + uint32_t arg_limit, + const char *** _arg_result); + +errno_t +sbus_call_ifp_users_ListByDomainAndName + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_domain_name, + const char * arg_name_filter, + uint32_t arg_limit, + const char *** _arg_result); + +errno_t +sbus_call_ifp_users_ListByName + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char * arg_name_filter, + uint32_t arg_limit, + const char *** _arg_result); + +errno_t +sbus_call_ifp_user_UpdateGroupsList + (struct sbus_sync_connection *conn, + const char *busname, + const char *object_path); + +errno_t +sbus_get_ifp_components_debug_level + (struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + uint32_t* _value); + +errno_t +sbus_get_ifp_components_enabled + (struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + bool* _value); + +errno_t +sbus_get_ifp_components_name + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char ** _value); + +errno_t +sbus_get_ifp_components_providers + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char *** _value); + +errno_t +sbus_get_ifp_components_type + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char ** _value); + +errno_t +sbus_get_ifp_domains_backup_servers + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char *** _value); + +errno_t +sbus_get_ifp_domains_enumerable + (struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + bool* _value); + +errno_t +sbus_get_ifp_domains_forest + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char ** _value); + +errno_t +sbus_get_ifp_domains_fully_qualified_name_format + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char ** _value); + +errno_t +sbus_get_ifp_domains_login_format + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char ** _value); + +errno_t +sbus_get_ifp_domains_max_id + (struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + uint32_t* _value); + +errno_t +sbus_get_ifp_domains_min_id + (struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + uint32_t* _value); + +errno_t +sbus_get_ifp_domains_name + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char ** _value); + +errno_t +sbus_get_ifp_domains_parent_domain + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char ** _value); + +errno_t +sbus_get_ifp_domains_primary_servers + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char *** _value); + +errno_t +sbus_get_ifp_domains_provider + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char ** _value); + +errno_t +sbus_get_ifp_domains_realm + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char ** _value); + +errno_t +sbus_get_ifp_domains_subdomain + (struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + bool* _value); + +errno_t +sbus_get_ifp_domains_use_fully_qualified_names + (struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + bool* _value); + +errno_t +sbus_get_ifp_group_gidNumber + (struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + uint32_t* _value); + +errno_t +sbus_get_ifp_group_groups + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char *** _value); + +errno_t +sbus_get_ifp_group_name + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char ** _value); + +errno_t +sbus_get_ifp_group_uniqueID + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char ** _value); + +errno_t +sbus_get_ifp_group_users + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char *** _value); + +errno_t +sbus_get_ifp_user_domain + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char ** _value); + +errno_t +sbus_get_ifp_user_domainname + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char ** _value); + +errno_t +sbus_get_ifp_user_extraAttributes + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + hash_table_t ** _value); + +errno_t +sbus_get_ifp_user_gecos + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char ** _value); + +errno_t +sbus_get_ifp_user_gidNumber + (struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + uint32_t* _value); + +errno_t +sbus_get_ifp_user_groups + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char *** _value); + +errno_t +sbus_get_ifp_user_homeDirectory + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char ** _value); + +errno_t +sbus_get_ifp_user_loginShell + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char ** _value); + +errno_t +sbus_get_ifp_user_name + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char ** _value); + +errno_t +sbus_get_ifp_user_uidNumber + (struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + uint32_t* _value); + +errno_t +sbus_get_ifp_user_uniqueID + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + const char ** _value); + +errno_t +sbus_getall_ifp_components + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + struct sbus_all_ifp_components **_properties); + +errno_t +sbus_getall_ifp_domains + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + struct sbus_all_ifp_domains **_properties); + +errno_t +sbus_getall_ifp_group + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + struct sbus_all_ifp_group **_properties); + +errno_t +sbus_getall_ifp_user + (TALLOC_CTX *mem_ctx, + struct sbus_sync_connection *conn, + const char *busname, + const char *object_path, + struct sbus_all_ifp_user **_properties); + +#endif /* _SBUS_IFP_CLIENT_SYNC_H_ */ diff --git a/src/responder/ifp/ifp_iface/sbus_ifp_interface.h b/src/responder/ifp/ifp_iface/sbus_ifp_interface.h new file mode 100644 index 0000000..0faac69 --- /dev/null +++ b/src/responder/ifp/ifp_iface/sbus_ifp_interface.h @@ -0,0 +1,2083 @@ +/* + Generated by sbus code generator + + Copyright (C) 2017 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/>. +*/ + +#ifndef _SBUS_IFP_INTERFACE_H_ +#define _SBUS_IFP_INTERFACE_H_ + +#include "sbus/sbus_interface_declarations.h" +#include "responder/ifp/ifp_iface/sbus_ifp_invokers.h" +#include "responder/ifp/ifp_iface/sbus_ifp_symbols.h" +#include "responder/ifp/ifp_iface/sbus_ifp_keygens.h" + +/* Interface: org.freedesktop.sssd.infopipe */ +#define SBUS_IFACE_org_freedesktop_sssd_infopipe(methods, signals, properties) ({ \ + sbus_interface("org.freedesktop.sssd.infopipe", NULL, \ + (methods), (signals), (properties)); \ +}) + +/* Method: org.freedesktop.sssd.infopipe.FindBackendByName */ +#define SBUS_METHOD_SYNC_org_freedesktop_sssd_infopipe_FindBackendByName(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char *, const char **); \ + sbus_method_sync("FindBackendByName", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_FindBackendByName, \ + NULL, \ + _sbus_ifp_invoke_in_s_out_o_send, \ + _sbus_ifp_key_s_0, \ + (handler), (data)); \ +}) + +#define SBUS_METHOD_ASYNC_org_freedesktop_sssd_infopipe_FindBackendByName(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data), const char *); \ + SBUS_CHECK_RECV((handler_recv), const char **); \ + sbus_method_async("FindBackendByName", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_FindBackendByName, \ + NULL, \ + _sbus_ifp_invoke_in_s_out_o_send, \ + _sbus_ifp_key_s_0, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Method: org.freedesktop.sssd.infopipe.FindDomainByName */ +#define SBUS_METHOD_SYNC_org_freedesktop_sssd_infopipe_FindDomainByName(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char *, const char **); \ + sbus_method_sync("FindDomainByName", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_FindDomainByName, \ + NULL, \ + _sbus_ifp_invoke_in_s_out_o_send, \ + _sbus_ifp_key_s_0, \ + (handler), (data)); \ +}) + +#define SBUS_METHOD_ASYNC_org_freedesktop_sssd_infopipe_FindDomainByName(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data), const char *); \ + SBUS_CHECK_RECV((handler_recv), const char **); \ + sbus_method_async("FindDomainByName", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_FindDomainByName, \ + NULL, \ + _sbus_ifp_invoke_in_s_out_o_send, \ + _sbus_ifp_key_s_0, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Method: org.freedesktop.sssd.infopipe.FindMonitor */ +#define SBUS_METHOD_SYNC_org_freedesktop_sssd_infopipe_FindMonitor(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char **); \ + sbus_method_sync("FindMonitor", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_FindMonitor, \ + NULL, \ + _sbus_ifp_invoke_in__out_o_send, \ + _sbus_ifp_key_, \ + (handler), (data)); \ +}) + +#define SBUS_METHOD_ASYNC_org_freedesktop_sssd_infopipe_FindMonitor(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), const char **); \ + sbus_method_async("FindMonitor", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_FindMonitor, \ + NULL, \ + _sbus_ifp_invoke_in__out_o_send, \ + _sbus_ifp_key_, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Method: org.freedesktop.sssd.infopipe.FindResponderByName */ +#define SBUS_METHOD_SYNC_org_freedesktop_sssd_infopipe_FindResponderByName(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char *, const char **); \ + sbus_method_sync("FindResponderByName", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_FindResponderByName, \ + NULL, \ + _sbus_ifp_invoke_in_s_out_o_send, \ + _sbus_ifp_key_s_0, \ + (handler), (data)); \ +}) + +#define SBUS_METHOD_ASYNC_org_freedesktop_sssd_infopipe_FindResponderByName(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data), const char *); \ + SBUS_CHECK_RECV((handler_recv), const char **); \ + sbus_method_async("FindResponderByName", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_FindResponderByName, \ + NULL, \ + _sbus_ifp_invoke_in_s_out_o_send, \ + _sbus_ifp_key_s_0, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Method: org.freedesktop.sssd.infopipe.GetUserAttr */ +#define SBUS_METHOD_SYNC_org_freedesktop_sssd_infopipe_GetUserAttr(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char *, const char **, DBusMessageIter *); \ + sbus_method_sync("GetUserAttr", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_GetUserAttr, \ + NULL, \ + _sbus_ifp_invoke_in_sas_out_raw_send, \ + NULL, \ + (handler), (data)); \ +}) + +#define SBUS_METHOD_ASYNC_org_freedesktop_sssd_infopipe_GetUserAttr(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data), const char *, const char **, DBusMessageIter *); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_method_async("GetUserAttr", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_GetUserAttr, \ + NULL, \ + _sbus_ifp_invoke_in_sas_out_raw_send, \ + NULL, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Method: org.freedesktop.sssd.infopipe.GetUserGroups */ +#define SBUS_METHOD_SYNC_org_freedesktop_sssd_infopipe_GetUserGroups(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char *, const char ***); \ + sbus_method_sync("GetUserGroups", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_GetUserGroups, \ + NULL, \ + _sbus_ifp_invoke_in_s_out_as_send, \ + _sbus_ifp_key_s_0, \ + (handler), (data)); \ +}) + +#define SBUS_METHOD_ASYNC_org_freedesktop_sssd_infopipe_GetUserGroups(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data), const char *); \ + SBUS_CHECK_RECV((handler_recv), const char ***); \ + sbus_method_async("GetUserGroups", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_GetUserGroups, \ + NULL, \ + _sbus_ifp_invoke_in_s_out_as_send, \ + _sbus_ifp_key_s_0, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Method: org.freedesktop.sssd.infopipe.ListBackends */ +#define SBUS_METHOD_SYNC_org_freedesktop_sssd_infopipe_ListBackends(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char ***); \ + sbus_method_sync("ListBackends", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_ListBackends, \ + NULL, \ + _sbus_ifp_invoke_in__out_ao_send, \ + _sbus_ifp_key_, \ + (handler), (data)); \ +}) + +#define SBUS_METHOD_ASYNC_org_freedesktop_sssd_infopipe_ListBackends(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), const char ***); \ + sbus_method_async("ListBackends", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_ListBackends, \ + NULL, \ + _sbus_ifp_invoke_in__out_ao_send, \ + _sbus_ifp_key_, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Method: org.freedesktop.sssd.infopipe.ListComponents */ +#define SBUS_METHOD_SYNC_org_freedesktop_sssd_infopipe_ListComponents(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char ***); \ + sbus_method_sync("ListComponents", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_ListComponents, \ + NULL, \ + _sbus_ifp_invoke_in__out_ao_send, \ + _sbus_ifp_key_, \ + (handler), (data)); \ +}) + +#define SBUS_METHOD_ASYNC_org_freedesktop_sssd_infopipe_ListComponents(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), const char ***); \ + sbus_method_async("ListComponents", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_ListComponents, \ + NULL, \ + _sbus_ifp_invoke_in__out_ao_send, \ + _sbus_ifp_key_, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Method: org.freedesktop.sssd.infopipe.ListDomains */ +#define SBUS_METHOD_SYNC_org_freedesktop_sssd_infopipe_ListDomains(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char ***); \ + sbus_method_sync("ListDomains", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_ListDomains, \ + NULL, \ + _sbus_ifp_invoke_in__out_ao_send, \ + _sbus_ifp_key_, \ + (handler), (data)); \ +}) + +#define SBUS_METHOD_ASYNC_org_freedesktop_sssd_infopipe_ListDomains(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), const char ***); \ + sbus_method_async("ListDomains", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_ListDomains, \ + NULL, \ + _sbus_ifp_invoke_in__out_ao_send, \ + _sbus_ifp_key_, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Method: org.freedesktop.sssd.infopipe.ListResponders */ +#define SBUS_METHOD_SYNC_org_freedesktop_sssd_infopipe_ListResponders(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char ***); \ + sbus_method_sync("ListResponders", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_ListResponders, \ + NULL, \ + _sbus_ifp_invoke_in__out_ao_send, \ + _sbus_ifp_key_, \ + (handler), (data)); \ +}) + +#define SBUS_METHOD_ASYNC_org_freedesktop_sssd_infopipe_ListResponders(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), const char ***); \ + sbus_method_async("ListResponders", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_ListResponders, \ + NULL, \ + _sbus_ifp_invoke_in__out_ao_send, \ + _sbus_ifp_key_, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Method: org.freedesktop.sssd.infopipe.Ping */ +#define SBUS_METHOD_SYNC_org_freedesktop_sssd_infopipe_Ping(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char *, const char **); \ + sbus_method_sync("Ping", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Ping, \ + NULL, \ + _sbus_ifp_invoke_in_s_out_s_send, \ + _sbus_ifp_key_s_0, \ + (handler), (data)); \ +}) + +#define SBUS_METHOD_ASYNC_org_freedesktop_sssd_infopipe_Ping(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data), const char *); \ + SBUS_CHECK_RECV((handler_recv), const char **); \ + sbus_method_async("Ping", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Ping, \ + NULL, \ + _sbus_ifp_invoke_in_s_out_s_send, \ + _sbus_ifp_key_s_0, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Interface: org.freedesktop.sssd.infopipe.Cache */ +#define SBUS_IFACE_org_freedesktop_sssd_infopipe_Cache(methods, signals, properties) ({ \ + sbus_interface("org.freedesktop.sssd.infopipe.Cache", NULL, \ + (methods), (signals), (properties)); \ +}) + +/* Method: org.freedesktop.sssd.infopipe.Cache.List */ +#define SBUS_METHOD_SYNC_org_freedesktop_sssd_infopipe_Cache_List(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char ***); \ + sbus_method_sync("List", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Cache_List, \ + NULL, \ + _sbus_ifp_invoke_in__out_ao_send, \ + _sbus_ifp_key_, \ + (handler), (data)); \ +}) + +#define SBUS_METHOD_ASYNC_org_freedesktop_sssd_infopipe_Cache_List(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), const char ***); \ + sbus_method_async("List", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Cache_List, \ + NULL, \ + _sbus_ifp_invoke_in__out_ao_send, \ + _sbus_ifp_key_, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Method: org.freedesktop.sssd.infopipe.Cache.ListByDomain */ +#define SBUS_METHOD_SYNC_org_freedesktop_sssd_infopipe_Cache_ListByDomain(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char *, const char ***); \ + sbus_method_sync("ListByDomain", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Cache_ListByDomain, \ + NULL, \ + _sbus_ifp_invoke_in_s_out_ao_send, \ + _sbus_ifp_key_s_0, \ + (handler), (data)); \ +}) + +#define SBUS_METHOD_ASYNC_org_freedesktop_sssd_infopipe_Cache_ListByDomain(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data), const char *); \ + SBUS_CHECK_RECV((handler_recv), const char ***); \ + sbus_method_async("ListByDomain", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Cache_ListByDomain, \ + NULL, \ + _sbus_ifp_invoke_in_s_out_ao_send, \ + _sbus_ifp_key_s_0, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Interface: org.freedesktop.sssd.infopipe.Cache.Object */ +#define SBUS_IFACE_org_freedesktop_sssd_infopipe_Cache_Object(methods, signals, properties) ({ \ + sbus_interface("org.freedesktop.sssd.infopipe.Cache.Object", NULL, \ + (methods), (signals), (properties)); \ +}) + +/* Method: org.freedesktop.sssd.infopipe.Cache.Object.Remove */ +#define SBUS_METHOD_SYNC_org_freedesktop_sssd_infopipe_Cache_Object_Remove(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), bool*); \ + sbus_method_sync("Remove", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Cache_Object_Remove, \ + NULL, \ + _sbus_ifp_invoke_in__out_b_send, \ + _sbus_ifp_key_, \ + (handler), (data)); \ +}) + +#define SBUS_METHOD_ASYNC_org_freedesktop_sssd_infopipe_Cache_Object_Remove(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), bool*); \ + sbus_method_async("Remove", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Cache_Object_Remove, \ + NULL, \ + _sbus_ifp_invoke_in__out_b_send, \ + _sbus_ifp_key_, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Method: org.freedesktop.sssd.infopipe.Cache.Object.Store */ +#define SBUS_METHOD_SYNC_org_freedesktop_sssd_infopipe_Cache_Object_Store(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), bool*); \ + sbus_method_sync("Store", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Cache_Object_Store, \ + NULL, \ + _sbus_ifp_invoke_in__out_b_send, \ + _sbus_ifp_key_, \ + (handler), (data)); \ +}) + +#define SBUS_METHOD_ASYNC_org_freedesktop_sssd_infopipe_Cache_Object_Store(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), bool*); \ + sbus_method_async("Store", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Cache_Object_Store, \ + NULL, \ + _sbus_ifp_invoke_in__out_b_send, \ + _sbus_ifp_key_, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Interface: org.freedesktop.sssd.infopipe.Components */ +#define SBUS_IFACE_org_freedesktop_sssd_infopipe_Components(methods, signals, properties) ({ \ + sbus_interface("org.freedesktop.sssd.infopipe.Components", NULL, \ + (methods), (signals), (properties)); \ +}) + +/* Property: org.freedesktop.sssd.infopipe.Components.debug_level */ +#define SBUS_GETTER_SYNC_org_freedesktop_sssd_infopipe_Components_debug_level(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), uint32_t*); \ + sbus_property_sync("debug_level", "u", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_u_send, \ + (handler), (data)); \ +}) + +#define SBUS_GETTER_ASYNC_org_freedesktop_sssd_infopipe_Components_debug_level(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), uint32_t*); \ + sbus_property_async("debug_level", "u", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_u_send, \ + (handler_send), (handler_recv), (data)); \ +}) + +#define SBUS_SETTER_SYNC_org_freedesktop_sssd_infopipe_Components_debug_level(handler, data) ({\ + SBUS_CHECK_SYNC((handler), (data)); \ + sbus_property_sync("debug_level", "u", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler), (data)); \ +}) + +#define SBUS_SETTER_ASYNC_org_freedesktop_sssd_infopipe_Components_debug_level(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_property_async("debug_level", "u", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Property: org.freedesktop.sssd.infopipe.Components.enabled */ +#define SBUS_GETTER_SYNC_org_freedesktop_sssd_infopipe_Components_enabled(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), bool*); \ + sbus_property_sync("enabled", "b", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_b_send, \ + (handler), (data)); \ +}) + +#define SBUS_GETTER_ASYNC_org_freedesktop_sssd_infopipe_Components_enabled(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), bool*); \ + sbus_property_async("enabled", "b", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_b_send, \ + (handler_send), (handler_recv), (data)); \ +}) + +#define SBUS_SETTER_SYNC_org_freedesktop_sssd_infopipe_Components_enabled(handler, data) ({\ + SBUS_CHECK_SYNC((handler), (data)); \ + sbus_property_sync("enabled", "b", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler), (data)); \ +}) + +#define SBUS_SETTER_ASYNC_org_freedesktop_sssd_infopipe_Components_enabled(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_property_async("enabled", "b", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Property: org.freedesktop.sssd.infopipe.Components.name */ +#define SBUS_GETTER_SYNC_org_freedesktop_sssd_infopipe_Components_name(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char **); \ + sbus_property_sync("name", "s", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_s_send, \ + (handler), (data)); \ +}) + +#define SBUS_GETTER_ASYNC_org_freedesktop_sssd_infopipe_Components_name(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), const char **); \ + sbus_property_async("name", "s", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_s_send, \ + (handler_send), (handler_recv), (data)); \ +}) + +#define SBUS_SETTER_SYNC_org_freedesktop_sssd_infopipe_Components_name(handler, data) ({\ + SBUS_CHECK_SYNC((handler), (data)); \ + sbus_property_sync("name", "s", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler), (data)); \ +}) + +#define SBUS_SETTER_ASYNC_org_freedesktop_sssd_infopipe_Components_name(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_property_async("name", "s", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Property: org.freedesktop.sssd.infopipe.Components.providers */ +#define SBUS_GETTER_SYNC_org_freedesktop_sssd_infopipe_Components_providers(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char ***); \ + sbus_property_sync("providers", "as", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_as_send, \ + (handler), (data)); \ +}) + +#define SBUS_GETTER_ASYNC_org_freedesktop_sssd_infopipe_Components_providers(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), const char ***); \ + sbus_property_async("providers", "as", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_as_send, \ + (handler_send), (handler_recv), (data)); \ +}) + +#define SBUS_SETTER_SYNC_org_freedesktop_sssd_infopipe_Components_providers(handler, data) ({\ + SBUS_CHECK_SYNC((handler), (data)); \ + sbus_property_sync("providers", "as", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler), (data)); \ +}) + +#define SBUS_SETTER_ASYNC_org_freedesktop_sssd_infopipe_Components_providers(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_property_async("providers", "as", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Property: org.freedesktop.sssd.infopipe.Components.type */ +#define SBUS_GETTER_SYNC_org_freedesktop_sssd_infopipe_Components_type(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char **); \ + sbus_property_sync("type", "s", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_s_send, \ + (handler), (data)); \ +}) + +#define SBUS_GETTER_ASYNC_org_freedesktop_sssd_infopipe_Components_type(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), const char **); \ + sbus_property_async("type", "s", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_s_send, \ + (handler_send), (handler_recv), (data)); \ +}) + +#define SBUS_SETTER_SYNC_org_freedesktop_sssd_infopipe_Components_type(handler, data) ({\ + SBUS_CHECK_SYNC((handler), (data)); \ + sbus_property_sync("type", "s", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler), (data)); \ +}) + +#define SBUS_SETTER_ASYNC_org_freedesktop_sssd_infopipe_Components_type(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_property_async("type", "s", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Interface: org.freedesktop.sssd.infopipe.Domains */ +#define SBUS_IFACE_org_freedesktop_sssd_infopipe_Domains(methods, signals, properties) ({ \ + sbus_interface("org.freedesktop.sssd.infopipe.Domains", NULL, \ + (methods), (signals), (properties)); \ +}) + +/* Property: org.freedesktop.sssd.infopipe.Domains.backup_servers */ +#define SBUS_GETTER_SYNC_org_freedesktop_sssd_infopipe_Domains_backup_servers(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char ***); \ + sbus_property_sync("backup_servers", "as", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_as_send, \ + (handler), (data)); \ +}) + +#define SBUS_GETTER_ASYNC_org_freedesktop_sssd_infopipe_Domains_backup_servers(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), const char ***); \ + sbus_property_async("backup_servers", "as", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_as_send, \ + (handler_send), (handler_recv), (data)); \ +}) + +#define SBUS_SETTER_SYNC_org_freedesktop_sssd_infopipe_Domains_backup_servers(handler, data) ({\ + SBUS_CHECK_SYNC((handler), (data)); \ + sbus_property_sync("backup_servers", "as", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler), (data)); \ +}) + +#define SBUS_SETTER_ASYNC_org_freedesktop_sssd_infopipe_Domains_backup_servers(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_property_async("backup_servers", "as", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Property: org.freedesktop.sssd.infopipe.Domains.enumerable */ +#define SBUS_GETTER_SYNC_org_freedesktop_sssd_infopipe_Domains_enumerable(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), bool*); \ + sbus_property_sync("enumerable", "b", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_b_send, \ + (handler), (data)); \ +}) + +#define SBUS_GETTER_ASYNC_org_freedesktop_sssd_infopipe_Domains_enumerable(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), bool*); \ + sbus_property_async("enumerable", "b", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_b_send, \ + (handler_send), (handler_recv), (data)); \ +}) + +#define SBUS_SETTER_SYNC_org_freedesktop_sssd_infopipe_Domains_enumerable(handler, data) ({\ + SBUS_CHECK_SYNC((handler), (data)); \ + sbus_property_sync("enumerable", "b", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler), (data)); \ +}) + +#define SBUS_SETTER_ASYNC_org_freedesktop_sssd_infopipe_Domains_enumerable(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_property_async("enumerable", "b", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Property: org.freedesktop.sssd.infopipe.Domains.forest */ +#define SBUS_GETTER_SYNC_org_freedesktop_sssd_infopipe_Domains_forest(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char **); \ + sbus_property_sync("forest", "s", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_s_send, \ + (handler), (data)); \ +}) + +#define SBUS_GETTER_ASYNC_org_freedesktop_sssd_infopipe_Domains_forest(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), const char **); \ + sbus_property_async("forest", "s", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_s_send, \ + (handler_send), (handler_recv), (data)); \ +}) + +#define SBUS_SETTER_SYNC_org_freedesktop_sssd_infopipe_Domains_forest(handler, data) ({\ + SBUS_CHECK_SYNC((handler), (data)); \ + sbus_property_sync("forest", "s", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler), (data)); \ +}) + +#define SBUS_SETTER_ASYNC_org_freedesktop_sssd_infopipe_Domains_forest(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_property_async("forest", "s", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Property: org.freedesktop.sssd.infopipe.Domains.fully_qualified_name_format */ +#define SBUS_GETTER_SYNC_org_freedesktop_sssd_infopipe_Domains_fully_qualified_name_format(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char **); \ + sbus_property_sync("fully_qualified_name_format", "s", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_s_send, \ + (handler), (data)); \ +}) + +#define SBUS_GETTER_ASYNC_org_freedesktop_sssd_infopipe_Domains_fully_qualified_name_format(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), const char **); \ + sbus_property_async("fully_qualified_name_format", "s", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_s_send, \ + (handler_send), (handler_recv), (data)); \ +}) + +#define SBUS_SETTER_SYNC_org_freedesktop_sssd_infopipe_Domains_fully_qualified_name_format(handler, data) ({\ + SBUS_CHECK_SYNC((handler), (data)); \ + sbus_property_sync("fully_qualified_name_format", "s", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler), (data)); \ +}) + +#define SBUS_SETTER_ASYNC_org_freedesktop_sssd_infopipe_Domains_fully_qualified_name_format(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_property_async("fully_qualified_name_format", "s", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Property: org.freedesktop.sssd.infopipe.Domains.login_format */ +#define SBUS_GETTER_SYNC_org_freedesktop_sssd_infopipe_Domains_login_format(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char **); \ + sbus_property_sync("login_format", "s", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_s_send, \ + (handler), (data)); \ +}) + +#define SBUS_GETTER_ASYNC_org_freedesktop_sssd_infopipe_Domains_login_format(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), const char **); \ + sbus_property_async("login_format", "s", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_s_send, \ + (handler_send), (handler_recv), (data)); \ +}) + +#define SBUS_SETTER_SYNC_org_freedesktop_sssd_infopipe_Domains_login_format(handler, data) ({\ + SBUS_CHECK_SYNC((handler), (data)); \ + sbus_property_sync("login_format", "s", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler), (data)); \ +}) + +#define SBUS_SETTER_ASYNC_org_freedesktop_sssd_infopipe_Domains_login_format(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_property_async("login_format", "s", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Property: org.freedesktop.sssd.infopipe.Domains.max_id */ +#define SBUS_GETTER_SYNC_org_freedesktop_sssd_infopipe_Domains_max_id(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), uint32_t*); \ + sbus_property_sync("max_id", "u", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_u_send, \ + (handler), (data)); \ +}) + +#define SBUS_GETTER_ASYNC_org_freedesktop_sssd_infopipe_Domains_max_id(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), uint32_t*); \ + sbus_property_async("max_id", "u", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_u_send, \ + (handler_send), (handler_recv), (data)); \ +}) + +#define SBUS_SETTER_SYNC_org_freedesktop_sssd_infopipe_Domains_max_id(handler, data) ({\ + SBUS_CHECK_SYNC((handler), (data)); \ + sbus_property_sync("max_id", "u", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler), (data)); \ +}) + +#define SBUS_SETTER_ASYNC_org_freedesktop_sssd_infopipe_Domains_max_id(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_property_async("max_id", "u", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Property: org.freedesktop.sssd.infopipe.Domains.min_id */ +#define SBUS_GETTER_SYNC_org_freedesktop_sssd_infopipe_Domains_min_id(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), uint32_t*); \ + sbus_property_sync("min_id", "u", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_u_send, \ + (handler), (data)); \ +}) + +#define SBUS_GETTER_ASYNC_org_freedesktop_sssd_infopipe_Domains_min_id(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), uint32_t*); \ + sbus_property_async("min_id", "u", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_u_send, \ + (handler_send), (handler_recv), (data)); \ +}) + +#define SBUS_SETTER_SYNC_org_freedesktop_sssd_infopipe_Domains_min_id(handler, data) ({\ + SBUS_CHECK_SYNC((handler), (data)); \ + sbus_property_sync("min_id", "u", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler), (data)); \ +}) + +#define SBUS_SETTER_ASYNC_org_freedesktop_sssd_infopipe_Domains_min_id(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_property_async("min_id", "u", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Property: org.freedesktop.sssd.infopipe.Domains.name */ +#define SBUS_GETTER_SYNC_org_freedesktop_sssd_infopipe_Domains_name(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char **); \ + sbus_property_sync("name", "s", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_s_send, \ + (handler), (data)); \ +}) + +#define SBUS_GETTER_ASYNC_org_freedesktop_sssd_infopipe_Domains_name(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), const char **); \ + sbus_property_async("name", "s", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_s_send, \ + (handler_send), (handler_recv), (data)); \ +}) + +#define SBUS_SETTER_SYNC_org_freedesktop_sssd_infopipe_Domains_name(handler, data) ({\ + SBUS_CHECK_SYNC((handler), (data)); \ + sbus_property_sync("name", "s", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler), (data)); \ +}) + +#define SBUS_SETTER_ASYNC_org_freedesktop_sssd_infopipe_Domains_name(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_property_async("name", "s", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Property: org.freedesktop.sssd.infopipe.Domains.parent_domain */ +#define SBUS_GETTER_SYNC_org_freedesktop_sssd_infopipe_Domains_parent_domain(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char **); \ + sbus_property_sync("parent_domain", "o", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_o_send, \ + (handler), (data)); \ +}) + +#define SBUS_GETTER_ASYNC_org_freedesktop_sssd_infopipe_Domains_parent_domain(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), const char **); \ + sbus_property_async("parent_domain", "o", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_o_send, \ + (handler_send), (handler_recv), (data)); \ +}) + +#define SBUS_SETTER_SYNC_org_freedesktop_sssd_infopipe_Domains_parent_domain(handler, data) ({\ + SBUS_CHECK_SYNC((handler), (data)); \ + sbus_property_sync("parent_domain", "o", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler), (data)); \ +}) + +#define SBUS_SETTER_ASYNC_org_freedesktop_sssd_infopipe_Domains_parent_domain(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_property_async("parent_domain", "o", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Property: org.freedesktop.sssd.infopipe.Domains.primary_servers */ +#define SBUS_GETTER_SYNC_org_freedesktop_sssd_infopipe_Domains_primary_servers(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char ***); \ + sbus_property_sync("primary_servers", "as", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_as_send, \ + (handler), (data)); \ +}) + +#define SBUS_GETTER_ASYNC_org_freedesktop_sssd_infopipe_Domains_primary_servers(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), const char ***); \ + sbus_property_async("primary_servers", "as", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_as_send, \ + (handler_send), (handler_recv), (data)); \ +}) + +#define SBUS_SETTER_SYNC_org_freedesktop_sssd_infopipe_Domains_primary_servers(handler, data) ({\ + SBUS_CHECK_SYNC((handler), (data)); \ + sbus_property_sync("primary_servers", "as", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler), (data)); \ +}) + +#define SBUS_SETTER_ASYNC_org_freedesktop_sssd_infopipe_Domains_primary_servers(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_property_async("primary_servers", "as", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Property: org.freedesktop.sssd.infopipe.Domains.provider */ +#define SBUS_GETTER_SYNC_org_freedesktop_sssd_infopipe_Domains_provider(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char **); \ + sbus_property_sync("provider", "s", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_s_send, \ + (handler), (data)); \ +}) + +#define SBUS_GETTER_ASYNC_org_freedesktop_sssd_infopipe_Domains_provider(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), const char **); \ + sbus_property_async("provider", "s", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_s_send, \ + (handler_send), (handler_recv), (data)); \ +}) + +#define SBUS_SETTER_SYNC_org_freedesktop_sssd_infopipe_Domains_provider(handler, data) ({\ + SBUS_CHECK_SYNC((handler), (data)); \ + sbus_property_sync("provider", "s", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler), (data)); \ +}) + +#define SBUS_SETTER_ASYNC_org_freedesktop_sssd_infopipe_Domains_provider(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_property_async("provider", "s", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Property: org.freedesktop.sssd.infopipe.Domains.realm */ +#define SBUS_GETTER_SYNC_org_freedesktop_sssd_infopipe_Domains_realm(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char **); \ + sbus_property_sync("realm", "s", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_s_send, \ + (handler), (data)); \ +}) + +#define SBUS_GETTER_ASYNC_org_freedesktop_sssd_infopipe_Domains_realm(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), const char **); \ + sbus_property_async("realm", "s", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_s_send, \ + (handler_send), (handler_recv), (data)); \ +}) + +#define SBUS_SETTER_SYNC_org_freedesktop_sssd_infopipe_Domains_realm(handler, data) ({\ + SBUS_CHECK_SYNC((handler), (data)); \ + sbus_property_sync("realm", "s", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler), (data)); \ +}) + +#define SBUS_SETTER_ASYNC_org_freedesktop_sssd_infopipe_Domains_realm(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_property_async("realm", "s", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Property: org.freedesktop.sssd.infopipe.Domains.subdomain */ +#define SBUS_GETTER_SYNC_org_freedesktop_sssd_infopipe_Domains_subdomain(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), bool*); \ + sbus_property_sync("subdomain", "b", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_b_send, \ + (handler), (data)); \ +}) + +#define SBUS_GETTER_ASYNC_org_freedesktop_sssd_infopipe_Domains_subdomain(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), bool*); \ + sbus_property_async("subdomain", "b", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_b_send, \ + (handler_send), (handler_recv), (data)); \ +}) + +#define SBUS_SETTER_SYNC_org_freedesktop_sssd_infopipe_Domains_subdomain(handler, data) ({\ + SBUS_CHECK_SYNC((handler), (data)); \ + sbus_property_sync("subdomain", "b", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler), (data)); \ +}) + +#define SBUS_SETTER_ASYNC_org_freedesktop_sssd_infopipe_Domains_subdomain(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_property_async("subdomain", "b", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Property: org.freedesktop.sssd.infopipe.Domains.use_fully_qualified_names */ +#define SBUS_GETTER_SYNC_org_freedesktop_sssd_infopipe_Domains_use_fully_qualified_names(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), bool*); \ + sbus_property_sync("use_fully_qualified_names", "b", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_b_send, \ + (handler), (data)); \ +}) + +#define SBUS_GETTER_ASYNC_org_freedesktop_sssd_infopipe_Domains_use_fully_qualified_names(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), bool*); \ + sbus_property_async("use_fully_qualified_names", "b", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_b_send, \ + (handler_send), (handler_recv), (data)); \ +}) + +#define SBUS_SETTER_SYNC_org_freedesktop_sssd_infopipe_Domains_use_fully_qualified_names(handler, data) ({\ + SBUS_CHECK_SYNC((handler), (data)); \ + sbus_property_sync("use_fully_qualified_names", "b", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler), (data)); \ +}) + +#define SBUS_SETTER_ASYNC_org_freedesktop_sssd_infopipe_Domains_use_fully_qualified_names(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_property_async("use_fully_qualified_names", "b", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Interface: org.freedesktop.sssd.infopipe.Domains.Domain */ +#define SBUS_IFACE_org_freedesktop_sssd_infopipe_Domains_Domain(methods, signals, properties) ({ \ + sbus_interface("org.freedesktop.sssd.infopipe.Domains.Domain", NULL, \ + (methods), (signals), (properties)); \ +}) + +/* Method: org.freedesktop.sssd.infopipe.Domains.Domain.ActiveServer */ +#define SBUS_METHOD_SYNC_org_freedesktop_sssd_infopipe_Domains_Domain_ActiveServer(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char *, const char **); \ + sbus_method_sync("ActiveServer", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Domains_Domain_ActiveServer, \ + NULL, \ + _sbus_ifp_invoke_in_s_out_s_send, \ + _sbus_ifp_key_s_0, \ + (handler), (data)); \ +}) + +#define SBUS_METHOD_ASYNC_org_freedesktop_sssd_infopipe_Domains_Domain_ActiveServer(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data), const char *); \ + SBUS_CHECK_RECV((handler_recv), const char **); \ + sbus_method_async("ActiveServer", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Domains_Domain_ActiveServer, \ + NULL, \ + _sbus_ifp_invoke_in_s_out_s_send, \ + _sbus_ifp_key_s_0, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Method: org.freedesktop.sssd.infopipe.Domains.Domain.IsOnline */ +#define SBUS_METHOD_SYNC_org_freedesktop_sssd_infopipe_Domains_Domain_IsOnline(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), bool*); \ + sbus_method_sync("IsOnline", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Domains_Domain_IsOnline, \ + NULL, \ + _sbus_ifp_invoke_in__out_b_send, \ + _sbus_ifp_key_, \ + (handler), (data)); \ +}) + +#define SBUS_METHOD_ASYNC_org_freedesktop_sssd_infopipe_Domains_Domain_IsOnline(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), bool*); \ + sbus_method_async("IsOnline", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Domains_Domain_IsOnline, \ + NULL, \ + _sbus_ifp_invoke_in__out_b_send, \ + _sbus_ifp_key_, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Method: org.freedesktop.sssd.infopipe.Domains.Domain.ListServers */ +#define SBUS_METHOD_SYNC_org_freedesktop_sssd_infopipe_Domains_Domain_ListServers(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char *, const char ***); \ + sbus_method_sync("ListServers", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Domains_Domain_ListServers, \ + NULL, \ + _sbus_ifp_invoke_in_s_out_as_send, \ + _sbus_ifp_key_s_0, \ + (handler), (data)); \ +}) + +#define SBUS_METHOD_ASYNC_org_freedesktop_sssd_infopipe_Domains_Domain_ListServers(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data), const char *); \ + SBUS_CHECK_RECV((handler_recv), const char ***); \ + sbus_method_async("ListServers", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Domains_Domain_ListServers, \ + NULL, \ + _sbus_ifp_invoke_in_s_out_as_send, \ + _sbus_ifp_key_s_0, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Method: org.freedesktop.sssd.infopipe.Domains.Domain.ListServices */ +#define SBUS_METHOD_SYNC_org_freedesktop_sssd_infopipe_Domains_Domain_ListServices(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char ***); \ + sbus_method_sync("ListServices", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Domains_Domain_ListServices, \ + NULL, \ + _sbus_ifp_invoke_in__out_as_send, \ + _sbus_ifp_key_, \ + (handler), (data)); \ +}) + +#define SBUS_METHOD_ASYNC_org_freedesktop_sssd_infopipe_Domains_Domain_ListServices(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), const char ***); \ + sbus_method_async("ListServices", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Domains_Domain_ListServices, \ + NULL, \ + _sbus_ifp_invoke_in__out_as_send, \ + _sbus_ifp_key_, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Method: org.freedesktop.sssd.infopipe.Domains.Domain.RefreshAccessRules */ +#define SBUS_METHOD_SYNC_org_freedesktop_sssd_infopipe_Domains_Domain_RefreshAccessRules(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data)); \ + sbus_method_sync("RefreshAccessRules", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Domains_Domain_RefreshAccessRules, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + _sbus_ifp_key_, \ + (handler), (data)); \ +}) + +#define SBUS_METHOD_ASYNC_org_freedesktop_sssd_infopipe_Domains_Domain_RefreshAccessRules(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_method_async("RefreshAccessRules", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Domains_Domain_RefreshAccessRules, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + _sbus_ifp_key_, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Interface: org.freedesktop.sssd.infopipe.Groups */ +#define SBUS_IFACE_org_freedesktop_sssd_infopipe_Groups(methods, signals, properties) ({ \ + sbus_interface("org.freedesktop.sssd.infopipe.Groups", NULL, \ + (methods), (signals), (properties)); \ +}) + +/* Method: org.freedesktop.sssd.infopipe.Groups.FindByID */ +#define SBUS_METHOD_SYNC_org_freedesktop_sssd_infopipe_Groups_FindByID(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), uint32_t, const char **); \ + sbus_method_sync("FindByID", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Groups_FindByID, \ + NULL, \ + _sbus_ifp_invoke_in_u_out_o_send, \ + _sbus_ifp_key_u_0, \ + (handler), (data)); \ +}) + +#define SBUS_METHOD_ASYNC_org_freedesktop_sssd_infopipe_Groups_FindByID(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data), uint32_t); \ + SBUS_CHECK_RECV((handler_recv), const char **); \ + sbus_method_async("FindByID", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Groups_FindByID, \ + NULL, \ + _sbus_ifp_invoke_in_u_out_o_send, \ + _sbus_ifp_key_u_0, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Method: org.freedesktop.sssd.infopipe.Groups.FindByName */ +#define SBUS_METHOD_SYNC_org_freedesktop_sssd_infopipe_Groups_FindByName(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char *, const char **); \ + sbus_method_sync("FindByName", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Groups_FindByName, \ + NULL, \ + _sbus_ifp_invoke_in_s_out_o_send, \ + _sbus_ifp_key_s_0, \ + (handler), (data)); \ +}) + +#define SBUS_METHOD_ASYNC_org_freedesktop_sssd_infopipe_Groups_FindByName(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data), const char *); \ + SBUS_CHECK_RECV((handler_recv), const char **); \ + sbus_method_async("FindByName", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Groups_FindByName, \ + NULL, \ + _sbus_ifp_invoke_in_s_out_o_send, \ + _sbus_ifp_key_s_0, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Method: org.freedesktop.sssd.infopipe.Groups.ListByDomainAndName */ +#define SBUS_METHOD_SYNC_org_freedesktop_sssd_infopipe_Groups_ListByDomainAndName(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char *, const char *, uint32_t, const char ***); \ + sbus_method_sync("ListByDomainAndName", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Groups_ListByDomainAndName, \ + NULL, \ + _sbus_ifp_invoke_in_ssu_out_ao_send, \ + _sbus_ifp_key_ssu_0_1_2, \ + (handler), (data)); \ +}) + +#define SBUS_METHOD_ASYNC_org_freedesktop_sssd_infopipe_Groups_ListByDomainAndName(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data), const char *, const char *, uint32_t); \ + SBUS_CHECK_RECV((handler_recv), const char ***); \ + sbus_method_async("ListByDomainAndName", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Groups_ListByDomainAndName, \ + NULL, \ + _sbus_ifp_invoke_in_ssu_out_ao_send, \ + _sbus_ifp_key_ssu_0_1_2, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Method: org.freedesktop.sssd.infopipe.Groups.ListByName */ +#define SBUS_METHOD_SYNC_org_freedesktop_sssd_infopipe_Groups_ListByName(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char *, uint32_t, const char ***); \ + sbus_method_sync("ListByName", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Groups_ListByName, \ + NULL, \ + _sbus_ifp_invoke_in_su_out_ao_send, \ + _sbus_ifp_key_su_0_1, \ + (handler), (data)); \ +}) + +#define SBUS_METHOD_ASYNC_org_freedesktop_sssd_infopipe_Groups_ListByName(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data), const char *, uint32_t); \ + SBUS_CHECK_RECV((handler_recv), const char ***); \ + sbus_method_async("ListByName", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Groups_ListByName, \ + NULL, \ + _sbus_ifp_invoke_in_su_out_ao_send, \ + _sbus_ifp_key_su_0_1, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Interface: org.freedesktop.sssd.infopipe.Groups.Group */ +#define SBUS_IFACE_org_freedesktop_sssd_infopipe_Groups_Group(methods, signals, properties) ({ \ + sbus_interface("org.freedesktop.sssd.infopipe.Groups.Group", NULL, \ + (methods), (signals), (properties)); \ +}) + +/* Method: org.freedesktop.sssd.infopipe.Groups.Group.UpdateMemberList */ +#define SBUS_METHOD_SYNC_org_freedesktop_sssd_infopipe_Groups_Group_UpdateMemberList(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data)); \ + sbus_method_sync("UpdateMemberList", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Groups_Group_UpdateMemberList, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + _sbus_ifp_key_, \ + (handler), (data)); \ +}) + +#define SBUS_METHOD_ASYNC_org_freedesktop_sssd_infopipe_Groups_Group_UpdateMemberList(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_method_async("UpdateMemberList", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Groups_Group_UpdateMemberList, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + _sbus_ifp_key_, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Property: org.freedesktop.sssd.infopipe.Groups.Group.gidNumber */ +#define SBUS_GETTER_SYNC_org_freedesktop_sssd_infopipe_Groups_Group_gidNumber(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), uint32_t*); \ + sbus_property_sync("gidNumber", "u", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_u_send, \ + (handler), (data)); \ +}) + +#define SBUS_GETTER_ASYNC_org_freedesktop_sssd_infopipe_Groups_Group_gidNumber(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), uint32_t*); \ + sbus_property_async("gidNumber", "u", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_u_send, \ + (handler_send), (handler_recv), (data)); \ +}) + +#define SBUS_SETTER_SYNC_org_freedesktop_sssd_infopipe_Groups_Group_gidNumber(handler, data) ({\ + SBUS_CHECK_SYNC((handler), (data)); \ + sbus_property_sync("gidNumber", "u", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler), (data)); \ +}) + +#define SBUS_SETTER_ASYNC_org_freedesktop_sssd_infopipe_Groups_Group_gidNumber(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_property_async("gidNumber", "u", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Property: org.freedesktop.sssd.infopipe.Groups.Group.groups */ +#define SBUS_GETTER_SYNC_org_freedesktop_sssd_infopipe_Groups_Group_groups(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char ***); \ + sbus_property_sync("groups", "ao", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_ao_send, \ + (handler), (data)); \ +}) + +#define SBUS_GETTER_ASYNC_org_freedesktop_sssd_infopipe_Groups_Group_groups(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), const char ***); \ + sbus_property_async("groups", "ao", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_ao_send, \ + (handler_send), (handler_recv), (data)); \ +}) + +#define SBUS_SETTER_SYNC_org_freedesktop_sssd_infopipe_Groups_Group_groups(handler, data) ({\ + SBUS_CHECK_SYNC((handler), (data)); \ + sbus_property_sync("groups", "ao", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler), (data)); \ +}) + +#define SBUS_SETTER_ASYNC_org_freedesktop_sssd_infopipe_Groups_Group_groups(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_property_async("groups", "ao", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Property: org.freedesktop.sssd.infopipe.Groups.Group.name */ +#define SBUS_GETTER_SYNC_org_freedesktop_sssd_infopipe_Groups_Group_name(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char **); \ + sbus_property_sync("name", "s", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_s_send, \ + (handler), (data)); \ +}) + +#define SBUS_GETTER_ASYNC_org_freedesktop_sssd_infopipe_Groups_Group_name(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), const char **); \ + sbus_property_async("name", "s", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_s_send, \ + (handler_send), (handler_recv), (data)); \ +}) + +#define SBUS_SETTER_SYNC_org_freedesktop_sssd_infopipe_Groups_Group_name(handler, data) ({\ + SBUS_CHECK_SYNC((handler), (data)); \ + sbus_property_sync("name", "s", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler), (data)); \ +}) + +#define SBUS_SETTER_ASYNC_org_freedesktop_sssd_infopipe_Groups_Group_name(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_property_async("name", "s", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Property: org.freedesktop.sssd.infopipe.Groups.Group.uniqueID */ +#define SBUS_GETTER_SYNC_org_freedesktop_sssd_infopipe_Groups_Group_uniqueID(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char **); \ + sbus_property_sync("uniqueID", "s", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_s_send, \ + (handler), (data)); \ +}) + +#define SBUS_GETTER_ASYNC_org_freedesktop_sssd_infopipe_Groups_Group_uniqueID(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), const char **); \ + sbus_property_async("uniqueID", "s", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_s_send, \ + (handler_send), (handler_recv), (data)); \ +}) + +#define SBUS_SETTER_SYNC_org_freedesktop_sssd_infopipe_Groups_Group_uniqueID(handler, data) ({\ + SBUS_CHECK_SYNC((handler), (data)); \ + sbus_property_sync("uniqueID", "s", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler), (data)); \ +}) + +#define SBUS_SETTER_ASYNC_org_freedesktop_sssd_infopipe_Groups_Group_uniqueID(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_property_async("uniqueID", "s", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Property: org.freedesktop.sssd.infopipe.Groups.Group.users */ +#define SBUS_GETTER_SYNC_org_freedesktop_sssd_infopipe_Groups_Group_users(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char ***); \ + sbus_property_sync("users", "ao", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_ao_send, \ + (handler), (data)); \ +}) + +#define SBUS_GETTER_ASYNC_org_freedesktop_sssd_infopipe_Groups_Group_users(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), const char ***); \ + sbus_property_async("users", "ao", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_ao_send, \ + (handler_send), (handler_recv), (data)); \ +}) + +#define SBUS_SETTER_SYNC_org_freedesktop_sssd_infopipe_Groups_Group_users(handler, data) ({\ + SBUS_CHECK_SYNC((handler), (data)); \ + sbus_property_sync("users", "ao", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler), (data)); \ +}) + +#define SBUS_SETTER_ASYNC_org_freedesktop_sssd_infopipe_Groups_Group_users(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_property_async("users", "ao", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Interface: org.freedesktop.sssd.infopipe.Users */ +#define SBUS_IFACE_org_freedesktop_sssd_infopipe_Users(methods, signals, properties) ({ \ + sbus_interface("org.freedesktop.sssd.infopipe.Users", NULL, \ + (methods), (signals), (properties)); \ +}) + +/* Method: org.freedesktop.sssd.infopipe.Users.FindByCertificate */ +#define SBUS_METHOD_SYNC_org_freedesktop_sssd_infopipe_Users_FindByCertificate(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char *, const char **); \ + sbus_method_sync("FindByCertificate", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_FindByCertificate, \ + NULL, \ + _sbus_ifp_invoke_in_s_out_o_send, \ + NULL, \ + (handler), (data)); \ +}) + +#define SBUS_METHOD_ASYNC_org_freedesktop_sssd_infopipe_Users_FindByCertificate(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data), const char *); \ + SBUS_CHECK_RECV((handler_recv), const char **); \ + sbus_method_async("FindByCertificate", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_FindByCertificate, \ + NULL, \ + _sbus_ifp_invoke_in_s_out_o_send, \ + NULL, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Method: org.freedesktop.sssd.infopipe.Users.FindByID */ +#define SBUS_METHOD_SYNC_org_freedesktop_sssd_infopipe_Users_FindByID(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), uint32_t, const char **); \ + sbus_method_sync("FindByID", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_FindByID, \ + NULL, \ + _sbus_ifp_invoke_in_u_out_o_send, \ + _sbus_ifp_key_u_0, \ + (handler), (data)); \ +}) + +#define SBUS_METHOD_ASYNC_org_freedesktop_sssd_infopipe_Users_FindByID(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data), uint32_t); \ + SBUS_CHECK_RECV((handler_recv), const char **); \ + sbus_method_async("FindByID", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_FindByID, \ + NULL, \ + _sbus_ifp_invoke_in_u_out_o_send, \ + _sbus_ifp_key_u_0, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Method: org.freedesktop.sssd.infopipe.Users.FindByName */ +#define SBUS_METHOD_SYNC_org_freedesktop_sssd_infopipe_Users_FindByName(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char *, const char **); \ + sbus_method_sync("FindByName", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_FindByName, \ + NULL, \ + _sbus_ifp_invoke_in_s_out_o_send, \ + _sbus_ifp_key_s_0, \ + (handler), (data)); \ +}) + +#define SBUS_METHOD_ASYNC_org_freedesktop_sssd_infopipe_Users_FindByName(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data), const char *); \ + SBUS_CHECK_RECV((handler_recv), const char **); \ + sbus_method_async("FindByName", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_FindByName, \ + NULL, \ + _sbus_ifp_invoke_in_s_out_o_send, \ + _sbus_ifp_key_s_0, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Method: org.freedesktop.sssd.infopipe.Users.FindByNameAndCertificate */ +#define SBUS_METHOD_SYNC_org_freedesktop_sssd_infopipe_Users_FindByNameAndCertificate(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char *, const char *, const char **); \ + sbus_method_sync("FindByNameAndCertificate", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_FindByNameAndCertificate, \ + NULL, \ + _sbus_ifp_invoke_in_ss_out_o_send, \ + NULL, \ + (handler), (data)); \ +}) + +#define SBUS_METHOD_ASYNC_org_freedesktop_sssd_infopipe_Users_FindByNameAndCertificate(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data), const char *, const char *); \ + SBUS_CHECK_RECV((handler_recv), const char **); \ + sbus_method_async("FindByNameAndCertificate", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_FindByNameAndCertificate, \ + NULL, \ + _sbus_ifp_invoke_in_ss_out_o_send, \ + NULL, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Method: org.freedesktop.sssd.infopipe.Users.FindByValidCertificate */ +#define SBUS_METHOD_SYNC_org_freedesktop_sssd_infopipe_Users_FindByValidCertificate(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char *, const char **); \ + sbus_method_sync("FindByValidCertificate", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_FindByValidCertificate, \ + NULL, \ + _sbus_ifp_invoke_in_s_out_o_send, \ + NULL, \ + (handler), (data)); \ +}) + +#define SBUS_METHOD_ASYNC_org_freedesktop_sssd_infopipe_Users_FindByValidCertificate(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data), const char *); \ + SBUS_CHECK_RECV((handler_recv), const char **); \ + sbus_method_async("FindByValidCertificate", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_FindByValidCertificate, \ + NULL, \ + _sbus_ifp_invoke_in_s_out_o_send, \ + NULL, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Method: org.freedesktop.sssd.infopipe.Users.ListByAttr */ +#define SBUS_METHOD_SYNC_org_freedesktop_sssd_infopipe_Users_ListByAttr(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char *, const char *, uint32_t, const char ***); \ + sbus_method_sync("ListByAttr", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_ListByAttr, \ + NULL, \ + _sbus_ifp_invoke_in_ssu_out_ao_send, \ + _sbus_ifp_key_ssu_0_1_2, \ + (handler), (data)); \ +}) + +#define SBUS_METHOD_ASYNC_org_freedesktop_sssd_infopipe_Users_ListByAttr(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data), const char *, const char *, uint32_t); \ + SBUS_CHECK_RECV((handler_recv), const char ***); \ + sbus_method_async("ListByAttr", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_ListByAttr, \ + NULL, \ + _sbus_ifp_invoke_in_ssu_out_ao_send, \ + _sbus_ifp_key_ssu_0_1_2, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Method: org.freedesktop.sssd.infopipe.Users.ListByCertificate */ +#define SBUS_METHOD_SYNC_org_freedesktop_sssd_infopipe_Users_ListByCertificate(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char *, uint32_t, const char ***); \ + sbus_method_sync("ListByCertificate", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_ListByCertificate, \ + NULL, \ + _sbus_ifp_invoke_in_su_out_ao_send, \ + NULL, \ + (handler), (data)); \ +}) + +#define SBUS_METHOD_ASYNC_org_freedesktop_sssd_infopipe_Users_ListByCertificate(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data), const char *, uint32_t); \ + SBUS_CHECK_RECV((handler_recv), const char ***); \ + sbus_method_async("ListByCertificate", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_ListByCertificate, \ + NULL, \ + _sbus_ifp_invoke_in_su_out_ao_send, \ + NULL, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Method: org.freedesktop.sssd.infopipe.Users.ListByDomainAndName */ +#define SBUS_METHOD_SYNC_org_freedesktop_sssd_infopipe_Users_ListByDomainAndName(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char *, const char *, uint32_t, const char ***); \ + sbus_method_sync("ListByDomainAndName", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_ListByDomainAndName, \ + NULL, \ + _sbus_ifp_invoke_in_ssu_out_ao_send, \ + _sbus_ifp_key_ssu_0_1_2, \ + (handler), (data)); \ +}) + +#define SBUS_METHOD_ASYNC_org_freedesktop_sssd_infopipe_Users_ListByDomainAndName(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data), const char *, const char *, uint32_t); \ + SBUS_CHECK_RECV((handler_recv), const char ***); \ + sbus_method_async("ListByDomainAndName", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_ListByDomainAndName, \ + NULL, \ + _sbus_ifp_invoke_in_ssu_out_ao_send, \ + _sbus_ifp_key_ssu_0_1_2, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Method: org.freedesktop.sssd.infopipe.Users.ListByName */ +#define SBUS_METHOD_SYNC_org_freedesktop_sssd_infopipe_Users_ListByName(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char *, uint32_t, const char ***); \ + sbus_method_sync("ListByName", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_ListByName, \ + NULL, \ + _sbus_ifp_invoke_in_su_out_ao_send, \ + _sbus_ifp_key_su_0_1, \ + (handler), (data)); \ +}) + +#define SBUS_METHOD_ASYNC_org_freedesktop_sssd_infopipe_Users_ListByName(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data), const char *, uint32_t); \ + SBUS_CHECK_RECV((handler_recv), const char ***); \ + sbus_method_async("ListByName", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_ListByName, \ + NULL, \ + _sbus_ifp_invoke_in_su_out_ao_send, \ + _sbus_ifp_key_su_0_1, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Interface: org.freedesktop.sssd.infopipe.Users.User */ +#define SBUS_IFACE_org_freedesktop_sssd_infopipe_Users_User(methods, signals, properties) ({ \ + sbus_interface("org.freedesktop.sssd.infopipe.Users.User", NULL, \ + (methods), (signals), (properties)); \ +}) + +/* Method: org.freedesktop.sssd.infopipe.Users.User.UpdateGroupsList */ +#define SBUS_METHOD_SYNC_org_freedesktop_sssd_infopipe_Users_User_UpdateGroupsList(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data)); \ + sbus_method_sync("UpdateGroupsList", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_User_UpdateGroupsList, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + _sbus_ifp_key_, \ + (handler), (data)); \ +}) + +#define SBUS_METHOD_ASYNC_org_freedesktop_sssd_infopipe_Users_User_UpdateGroupsList(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_method_async("UpdateGroupsList", \ + &_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_User_UpdateGroupsList, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + _sbus_ifp_key_, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Property: org.freedesktop.sssd.infopipe.Users.User.domain */ +#define SBUS_GETTER_SYNC_org_freedesktop_sssd_infopipe_Users_User_domain(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char **); \ + sbus_property_sync("domain", "o", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_o_send, \ + (handler), (data)); \ +}) + +#define SBUS_GETTER_ASYNC_org_freedesktop_sssd_infopipe_Users_User_domain(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), const char **); \ + sbus_property_async("domain", "o", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_o_send, \ + (handler_send), (handler_recv), (data)); \ +}) + +#define SBUS_SETTER_SYNC_org_freedesktop_sssd_infopipe_Users_User_domain(handler, data) ({\ + SBUS_CHECK_SYNC((handler), (data)); \ + sbus_property_sync("domain", "o", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler), (data)); \ +}) + +#define SBUS_SETTER_ASYNC_org_freedesktop_sssd_infopipe_Users_User_domain(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_property_async("domain", "o", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Property: org.freedesktop.sssd.infopipe.Users.User.domainname */ +#define SBUS_GETTER_SYNC_org_freedesktop_sssd_infopipe_Users_User_domainname(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char **); \ + sbus_property_sync("domainname", "s", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_s_send, \ + (handler), (data)); \ +}) + +#define SBUS_GETTER_ASYNC_org_freedesktop_sssd_infopipe_Users_User_domainname(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), const char **); \ + sbus_property_async("domainname", "s", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_s_send, \ + (handler_send), (handler_recv), (data)); \ +}) + +#define SBUS_SETTER_SYNC_org_freedesktop_sssd_infopipe_Users_User_domainname(handler, data) ({\ + SBUS_CHECK_SYNC((handler), (data)); \ + sbus_property_sync("domainname", "s", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler), (data)); \ +}) + +#define SBUS_SETTER_ASYNC_org_freedesktop_sssd_infopipe_Users_User_domainname(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_property_async("domainname", "s", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Property: org.freedesktop.sssd.infopipe.Users.User.extraAttributes */ +#define SBUS_GETTER_SYNC_org_freedesktop_sssd_infopipe_Users_User_extraAttributes(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), hash_table_t **); \ + sbus_property_sync("extraAttributes", "a{sas}", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_ifp_extra_send, \ + (handler), (data)); \ +}) + +#define SBUS_GETTER_ASYNC_org_freedesktop_sssd_infopipe_Users_User_extraAttributes(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), hash_table_t **); \ + sbus_property_async("extraAttributes", "a{sas}", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_ifp_extra_send, \ + (handler_send), (handler_recv), (data)); \ +}) + +#define SBUS_SETTER_SYNC_org_freedesktop_sssd_infopipe_Users_User_extraAttributes(handler, data) ({\ + SBUS_CHECK_SYNC((handler), (data)); \ + sbus_property_sync("extraAttributes", "a{sas}", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler), (data)); \ +}) + +#define SBUS_SETTER_ASYNC_org_freedesktop_sssd_infopipe_Users_User_extraAttributes(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_property_async("extraAttributes", "a{sas}", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Property: org.freedesktop.sssd.infopipe.Users.User.gecos */ +#define SBUS_GETTER_SYNC_org_freedesktop_sssd_infopipe_Users_User_gecos(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char **); \ + sbus_property_sync("gecos", "s", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_s_send, \ + (handler), (data)); \ +}) + +#define SBUS_GETTER_ASYNC_org_freedesktop_sssd_infopipe_Users_User_gecos(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), const char **); \ + sbus_property_async("gecos", "s", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_s_send, \ + (handler_send), (handler_recv), (data)); \ +}) + +#define SBUS_SETTER_SYNC_org_freedesktop_sssd_infopipe_Users_User_gecos(handler, data) ({\ + SBUS_CHECK_SYNC((handler), (data)); \ + sbus_property_sync("gecos", "s", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler), (data)); \ +}) + +#define SBUS_SETTER_ASYNC_org_freedesktop_sssd_infopipe_Users_User_gecos(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_property_async("gecos", "s", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Property: org.freedesktop.sssd.infopipe.Users.User.gidNumber */ +#define SBUS_GETTER_SYNC_org_freedesktop_sssd_infopipe_Users_User_gidNumber(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), uint32_t*); \ + sbus_property_sync("gidNumber", "u", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_u_send, \ + (handler), (data)); \ +}) + +#define SBUS_GETTER_ASYNC_org_freedesktop_sssd_infopipe_Users_User_gidNumber(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), uint32_t*); \ + sbus_property_async("gidNumber", "u", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_u_send, \ + (handler_send), (handler_recv), (data)); \ +}) + +#define SBUS_SETTER_SYNC_org_freedesktop_sssd_infopipe_Users_User_gidNumber(handler, data) ({\ + SBUS_CHECK_SYNC((handler), (data)); \ + sbus_property_sync("gidNumber", "u", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler), (data)); \ +}) + +#define SBUS_SETTER_ASYNC_org_freedesktop_sssd_infopipe_Users_User_gidNumber(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_property_async("gidNumber", "u", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Property: org.freedesktop.sssd.infopipe.Users.User.groups */ +#define SBUS_GETTER_SYNC_org_freedesktop_sssd_infopipe_Users_User_groups(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char ***); \ + sbus_property_sync("groups", "ao", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_ao_send, \ + (handler), (data)); \ +}) + +#define SBUS_GETTER_ASYNC_org_freedesktop_sssd_infopipe_Users_User_groups(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), const char ***); \ + sbus_property_async("groups", "ao", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_ao_send, \ + (handler_send), (handler_recv), (data)); \ +}) + +#define SBUS_SETTER_SYNC_org_freedesktop_sssd_infopipe_Users_User_groups(handler, data) ({\ + SBUS_CHECK_SYNC((handler), (data)); \ + sbus_property_sync("groups", "ao", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler), (data)); \ +}) + +#define SBUS_SETTER_ASYNC_org_freedesktop_sssd_infopipe_Users_User_groups(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_property_async("groups", "ao", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Property: org.freedesktop.sssd.infopipe.Users.User.homeDirectory */ +#define SBUS_GETTER_SYNC_org_freedesktop_sssd_infopipe_Users_User_homeDirectory(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char **); \ + sbus_property_sync("homeDirectory", "s", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_s_send, \ + (handler), (data)); \ +}) + +#define SBUS_GETTER_ASYNC_org_freedesktop_sssd_infopipe_Users_User_homeDirectory(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), const char **); \ + sbus_property_async("homeDirectory", "s", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_s_send, \ + (handler_send), (handler_recv), (data)); \ +}) + +#define SBUS_SETTER_SYNC_org_freedesktop_sssd_infopipe_Users_User_homeDirectory(handler, data) ({\ + SBUS_CHECK_SYNC((handler), (data)); \ + sbus_property_sync("homeDirectory", "s", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler), (data)); \ +}) + +#define SBUS_SETTER_ASYNC_org_freedesktop_sssd_infopipe_Users_User_homeDirectory(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_property_async("homeDirectory", "s", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Property: org.freedesktop.sssd.infopipe.Users.User.loginShell */ +#define SBUS_GETTER_SYNC_org_freedesktop_sssd_infopipe_Users_User_loginShell(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char **); \ + sbus_property_sync("loginShell", "s", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_s_send, \ + (handler), (data)); \ +}) + +#define SBUS_GETTER_ASYNC_org_freedesktop_sssd_infopipe_Users_User_loginShell(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), const char **); \ + sbus_property_async("loginShell", "s", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_s_send, \ + (handler_send), (handler_recv), (data)); \ +}) + +#define SBUS_SETTER_SYNC_org_freedesktop_sssd_infopipe_Users_User_loginShell(handler, data) ({\ + SBUS_CHECK_SYNC((handler), (data)); \ + sbus_property_sync("loginShell", "s", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler), (data)); \ +}) + +#define SBUS_SETTER_ASYNC_org_freedesktop_sssd_infopipe_Users_User_loginShell(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_property_async("loginShell", "s", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Property: org.freedesktop.sssd.infopipe.Users.User.name */ +#define SBUS_GETTER_SYNC_org_freedesktop_sssd_infopipe_Users_User_name(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char **); \ + sbus_property_sync("name", "s", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_s_send, \ + (handler), (data)); \ +}) + +#define SBUS_GETTER_ASYNC_org_freedesktop_sssd_infopipe_Users_User_name(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), const char **); \ + sbus_property_async("name", "s", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_s_send, \ + (handler_send), (handler_recv), (data)); \ +}) + +#define SBUS_SETTER_SYNC_org_freedesktop_sssd_infopipe_Users_User_name(handler, data) ({\ + SBUS_CHECK_SYNC((handler), (data)); \ + sbus_property_sync("name", "s", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler), (data)); \ +}) + +#define SBUS_SETTER_ASYNC_org_freedesktop_sssd_infopipe_Users_User_name(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_property_async("name", "s", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Property: org.freedesktop.sssd.infopipe.Users.User.uidNumber */ +#define SBUS_GETTER_SYNC_org_freedesktop_sssd_infopipe_Users_User_uidNumber(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), uint32_t*); \ + sbus_property_sync("uidNumber", "u", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_u_send, \ + (handler), (data)); \ +}) + +#define SBUS_GETTER_ASYNC_org_freedesktop_sssd_infopipe_Users_User_uidNumber(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), uint32_t*); \ + sbus_property_async("uidNumber", "u", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_u_send, \ + (handler_send), (handler_recv), (data)); \ +}) + +#define SBUS_SETTER_SYNC_org_freedesktop_sssd_infopipe_Users_User_uidNumber(handler, data) ({\ + SBUS_CHECK_SYNC((handler), (data)); \ + sbus_property_sync("uidNumber", "u", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler), (data)); \ +}) + +#define SBUS_SETTER_ASYNC_org_freedesktop_sssd_infopipe_Users_User_uidNumber(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_property_async("uidNumber", "u", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler_send), (handler_recv), (data)); \ +}) + +/* Property: org.freedesktop.sssd.infopipe.Users.User.uniqueID */ +#define SBUS_GETTER_SYNC_org_freedesktop_sssd_infopipe_Users_User_uniqueID(handler, data) ({ \ + SBUS_CHECK_SYNC((handler), (data), const char **); \ + sbus_property_sync("uniqueID", "s", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_s_send, \ + (handler), (data)); \ +}) + +#define SBUS_GETTER_ASYNC_org_freedesktop_sssd_infopipe_Users_User_uniqueID(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv), const char **); \ + sbus_property_async("uniqueID", "s", SBUS_PROPERTY_READABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out_s_send, \ + (handler_send), (handler_recv), (data)); \ +}) + +#define SBUS_SETTER_SYNC_org_freedesktop_sssd_infopipe_Users_User_uniqueID(handler, data) ({\ + SBUS_CHECK_SYNC((handler), (data)); \ + sbus_property_sync("uniqueID", "s", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler), (data)); \ +}) + +#define SBUS_SETTER_ASYNC_org_freedesktop_sssd_infopipe_Users_User_uniqueID(handler_send, handler_recv, data) ({ \ + SBUS_CHECK_SEND((handler_send), (data)); \ + SBUS_CHECK_RECV((handler_recv)); \ + sbus_property_async("uniqueID", "s", SBUS_PROPERTY_WRITABLE, \ + NULL, \ + _sbus_ifp_invoke_in__out__send, \ + (handler_send), (handler_recv), (data)); \ +}) + +#endif /* _SBUS_IFP_INTERFACE_H_ */ diff --git a/src/responder/ifp/ifp_iface/sbus_ifp_invokers.c b/src/responder/ifp/ifp_iface/sbus_ifp_invokers.c new file mode 100644 index 0000000..a2db826 --- /dev/null +++ b/src/responder/ifp/ifp_iface/sbus_ifp_invokers.c @@ -0,0 +1,3004 @@ +/* + Generated by sbus code generator + + Copyright (C) 2017 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 <talloc.h> +#include <tevent.h> +#include <dbus/dbus.h> + +#include "sbus/sbus_private.h" +#include "sbus/sbus_interface_declarations.h" +#include "responder/ifp/ifp_iface/sbus_ifp_arguments.h" +#include "responder/ifp/ifp_iface/sbus_ifp_invokers.h" + +static errno_t +sbus_invoker_schedule(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + void *handler, + void *private_data) +{ + /* Schedule invoker as a timed event so it is processed after other + * event types. This will give dispatcher a chance to catch more + * messages before this invoker is triggered and therefore it will + * allow to potentially chain more request into one, especially for + * synchronous handlers. */ + + struct tevent_timer *te; + struct timeval tv; + + tv = tevent_timeval_current_ofs(0, 5); + te = tevent_add_timer(ev, mem_ctx, tv, handler, private_data); + if (te == NULL) { + /* There is not enough memory to create a timer. We can't do + * anything about it. */ + DEBUG(SSSDBG_OP_FAILURE, "Could not add invoker event!\n"); + return ENOMEM; + } + + return EOK; +} + +struct _sbus_ifp_invoke_in__out__state { + struct { + enum sbus_handler_type type; + void *data; + errno_t (*sync)(TALLOC_CTX *, struct sbus_request *, void *); + struct tevent_req * (*send)(TALLOC_CTX *, struct tevent_context *, struct sbus_request *, void *); + errno_t (*recv)(TALLOC_CTX *, struct tevent_req *); + } handler; + + struct sbus_request *sbus_req; + DBusMessageIter *read_iterator; + DBusMessageIter *write_iterator; +}; + +static void +_sbus_ifp_invoke_in__out__step + (struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *private_data); + +static void +_sbus_ifp_invoke_in__out__done + (struct tevent_req *subreq); + +struct tevent_req * +_sbus_ifp_invoke_in__out__send + (TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + sbus_invoker_keygen keygen, + const struct sbus_handler *handler, + DBusMessageIter *read_iterator, + DBusMessageIter *write_iterator, + const char **_key) +{ + struct _sbus_ifp_invoke_in__out__state *state; + struct tevent_req *req; + const char *key; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct _sbus_ifp_invoke_in__out__state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + state->handler.type = handler->type; + state->handler.data = handler->data; + state->handler.sync = handler->sync; + state->handler.send = handler->async_send; + state->handler.recv = handler->async_recv; + + state->sbus_req = sbus_req; + state->read_iterator = read_iterator; + state->write_iterator = write_iterator; + + ret = sbus_invoker_schedule(state, ev, _sbus_ifp_invoke_in__out__step, req); + if (ret != EOK) { + goto done; + } + + ret = sbus_request_key(state, keygen, sbus_req, NULL, &key); + if (ret != EOK) { + goto done; + } + + if (_key != NULL) { + *_key = talloc_steal(mem_ctx, key); + } + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void _sbus_ifp_invoke_in__out__step + (struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *private_data) +{ + struct _sbus_ifp_invoke_in__out__state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = talloc_get_type(private_data, struct tevent_req); + state = tevent_req_data(req, struct _sbus_ifp_invoke_in__out__state); + + switch (state->handler.type) { + case SBUS_HANDLER_SYNC: + if (state->handler.sync == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: sync handler is not specified!\n"); + ret = ERR_INTERNAL; + goto done; + } + + ret = state->handler.sync(state, state->sbus_req, state->handler.data); + if (ret != EOK) { + goto done; + } + + goto done; + case SBUS_HANDLER_ASYNC: + if (state->handler.send == NULL || state->handler.recv == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: async handler is not specified!\n"); + ret = ERR_INTERNAL; + goto done; + } + + subreq = state->handler.send(state, ev, state->sbus_req, state->handler.data); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, _sbus_ifp_invoke_in__out__done, req); + ret = EAGAIN; + goto done; + } + + ret = ERR_INTERNAL; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } +} + +static void _sbus_ifp_invoke_in__out__done(struct tevent_req *subreq) +{ + struct _sbus_ifp_invoke_in__out__state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct _sbus_ifp_invoke_in__out__state); + + ret = state->handler.recv(state, subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +struct _sbus_ifp_invoke_in__out_ao_state { + struct _sbus_ifp_invoker_args_ao out; + struct { + enum sbus_handler_type type; + void *data; + errno_t (*sync)(TALLOC_CTX *, struct sbus_request *, void *, const char ***); + struct tevent_req * (*send)(TALLOC_CTX *, struct tevent_context *, struct sbus_request *, void *); + errno_t (*recv)(TALLOC_CTX *, struct tevent_req *, const char ***); + } handler; + + struct sbus_request *sbus_req; + DBusMessageIter *read_iterator; + DBusMessageIter *write_iterator; +}; + +static void +_sbus_ifp_invoke_in__out_ao_step + (struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *private_data); + +static void +_sbus_ifp_invoke_in__out_ao_done + (struct tevent_req *subreq); + +struct tevent_req * +_sbus_ifp_invoke_in__out_ao_send + (TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + sbus_invoker_keygen keygen, + const struct sbus_handler *handler, + DBusMessageIter *read_iterator, + DBusMessageIter *write_iterator, + const char **_key) +{ + struct _sbus_ifp_invoke_in__out_ao_state *state; + struct tevent_req *req; + const char *key; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct _sbus_ifp_invoke_in__out_ao_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + state->handler.type = handler->type; + state->handler.data = handler->data; + state->handler.sync = handler->sync; + state->handler.send = handler->async_send; + state->handler.recv = handler->async_recv; + + state->sbus_req = sbus_req; + state->read_iterator = read_iterator; + state->write_iterator = write_iterator; + + ret = sbus_invoker_schedule(state, ev, _sbus_ifp_invoke_in__out_ao_step, req); + if (ret != EOK) { + goto done; + } + + ret = sbus_request_key(state, keygen, sbus_req, NULL, &key); + if (ret != EOK) { + goto done; + } + + if (_key != NULL) { + *_key = talloc_steal(mem_ctx, key); + } + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void _sbus_ifp_invoke_in__out_ao_step + (struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *private_data) +{ + struct _sbus_ifp_invoke_in__out_ao_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = talloc_get_type(private_data, struct tevent_req); + state = tevent_req_data(req, struct _sbus_ifp_invoke_in__out_ao_state); + + switch (state->handler.type) { + case SBUS_HANDLER_SYNC: + if (state->handler.sync == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: sync handler is not specified!\n"); + ret = ERR_INTERNAL; + goto done; + } + + ret = state->handler.sync(state, state->sbus_req, state->handler.data, &state->out.arg0); + if (ret != EOK) { + goto done; + } + + ret = _sbus_ifp_invoker_write_ao(state->write_iterator, &state->out); + goto done; + case SBUS_HANDLER_ASYNC: + if (state->handler.send == NULL || state->handler.recv == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: async handler is not specified!\n"); + ret = ERR_INTERNAL; + goto done; + } + + subreq = state->handler.send(state, ev, state->sbus_req, state->handler.data); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, _sbus_ifp_invoke_in__out_ao_done, req); + ret = EAGAIN; + goto done; + } + + ret = ERR_INTERNAL; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } +} + +static void _sbus_ifp_invoke_in__out_ao_done(struct tevent_req *subreq) +{ + struct _sbus_ifp_invoke_in__out_ao_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct _sbus_ifp_invoke_in__out_ao_state); + + ret = state->handler.recv(state, subreq, &state->out.arg0); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + ret = _sbus_ifp_invoker_write_ao(state->write_iterator, &state->out); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +struct _sbus_ifp_invoke_in__out_as_state { + struct _sbus_ifp_invoker_args_as out; + struct { + enum sbus_handler_type type; + void *data; + errno_t (*sync)(TALLOC_CTX *, struct sbus_request *, void *, const char ***); + struct tevent_req * (*send)(TALLOC_CTX *, struct tevent_context *, struct sbus_request *, void *); + errno_t (*recv)(TALLOC_CTX *, struct tevent_req *, const char ***); + } handler; + + struct sbus_request *sbus_req; + DBusMessageIter *read_iterator; + DBusMessageIter *write_iterator; +}; + +static void +_sbus_ifp_invoke_in__out_as_step + (struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *private_data); + +static void +_sbus_ifp_invoke_in__out_as_done + (struct tevent_req *subreq); + +struct tevent_req * +_sbus_ifp_invoke_in__out_as_send + (TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + sbus_invoker_keygen keygen, + const struct sbus_handler *handler, + DBusMessageIter *read_iterator, + DBusMessageIter *write_iterator, + const char **_key) +{ + struct _sbus_ifp_invoke_in__out_as_state *state; + struct tevent_req *req; + const char *key; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct _sbus_ifp_invoke_in__out_as_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + state->handler.type = handler->type; + state->handler.data = handler->data; + state->handler.sync = handler->sync; + state->handler.send = handler->async_send; + state->handler.recv = handler->async_recv; + + state->sbus_req = sbus_req; + state->read_iterator = read_iterator; + state->write_iterator = write_iterator; + + ret = sbus_invoker_schedule(state, ev, _sbus_ifp_invoke_in__out_as_step, req); + if (ret != EOK) { + goto done; + } + + ret = sbus_request_key(state, keygen, sbus_req, NULL, &key); + if (ret != EOK) { + goto done; + } + + if (_key != NULL) { + *_key = talloc_steal(mem_ctx, key); + } + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void _sbus_ifp_invoke_in__out_as_step + (struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *private_data) +{ + struct _sbus_ifp_invoke_in__out_as_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = talloc_get_type(private_data, struct tevent_req); + state = tevent_req_data(req, struct _sbus_ifp_invoke_in__out_as_state); + + switch (state->handler.type) { + case SBUS_HANDLER_SYNC: + if (state->handler.sync == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: sync handler is not specified!\n"); + ret = ERR_INTERNAL; + goto done; + } + + ret = state->handler.sync(state, state->sbus_req, state->handler.data, &state->out.arg0); + if (ret != EOK) { + goto done; + } + + ret = _sbus_ifp_invoker_write_as(state->write_iterator, &state->out); + goto done; + case SBUS_HANDLER_ASYNC: + if (state->handler.send == NULL || state->handler.recv == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: async handler is not specified!\n"); + ret = ERR_INTERNAL; + goto done; + } + + subreq = state->handler.send(state, ev, state->sbus_req, state->handler.data); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, _sbus_ifp_invoke_in__out_as_done, req); + ret = EAGAIN; + goto done; + } + + ret = ERR_INTERNAL; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } +} + +static void _sbus_ifp_invoke_in__out_as_done(struct tevent_req *subreq) +{ + struct _sbus_ifp_invoke_in__out_as_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct _sbus_ifp_invoke_in__out_as_state); + + ret = state->handler.recv(state, subreq, &state->out.arg0); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + ret = _sbus_ifp_invoker_write_as(state->write_iterator, &state->out); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +struct _sbus_ifp_invoke_in__out_b_state { + struct _sbus_ifp_invoker_args_b out; + struct { + enum sbus_handler_type type; + void *data; + errno_t (*sync)(TALLOC_CTX *, struct sbus_request *, void *, bool*); + struct tevent_req * (*send)(TALLOC_CTX *, struct tevent_context *, struct sbus_request *, void *); + errno_t (*recv)(TALLOC_CTX *, struct tevent_req *, bool*); + } handler; + + struct sbus_request *sbus_req; + DBusMessageIter *read_iterator; + DBusMessageIter *write_iterator; +}; + +static void +_sbus_ifp_invoke_in__out_b_step + (struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *private_data); + +static void +_sbus_ifp_invoke_in__out_b_done + (struct tevent_req *subreq); + +struct tevent_req * +_sbus_ifp_invoke_in__out_b_send + (TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + sbus_invoker_keygen keygen, + const struct sbus_handler *handler, + DBusMessageIter *read_iterator, + DBusMessageIter *write_iterator, + const char **_key) +{ + struct _sbus_ifp_invoke_in__out_b_state *state; + struct tevent_req *req; + const char *key; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct _sbus_ifp_invoke_in__out_b_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + state->handler.type = handler->type; + state->handler.data = handler->data; + state->handler.sync = handler->sync; + state->handler.send = handler->async_send; + state->handler.recv = handler->async_recv; + + state->sbus_req = sbus_req; + state->read_iterator = read_iterator; + state->write_iterator = write_iterator; + + ret = sbus_invoker_schedule(state, ev, _sbus_ifp_invoke_in__out_b_step, req); + if (ret != EOK) { + goto done; + } + + ret = sbus_request_key(state, keygen, sbus_req, NULL, &key); + if (ret != EOK) { + goto done; + } + + if (_key != NULL) { + *_key = talloc_steal(mem_ctx, key); + } + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void _sbus_ifp_invoke_in__out_b_step + (struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *private_data) +{ + struct _sbus_ifp_invoke_in__out_b_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = talloc_get_type(private_data, struct tevent_req); + state = tevent_req_data(req, struct _sbus_ifp_invoke_in__out_b_state); + + switch (state->handler.type) { + case SBUS_HANDLER_SYNC: + if (state->handler.sync == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: sync handler is not specified!\n"); + ret = ERR_INTERNAL; + goto done; + } + + ret = state->handler.sync(state, state->sbus_req, state->handler.data, &state->out.arg0); + if (ret != EOK) { + goto done; + } + + ret = _sbus_ifp_invoker_write_b(state->write_iterator, &state->out); + goto done; + case SBUS_HANDLER_ASYNC: + if (state->handler.send == NULL || state->handler.recv == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: async handler is not specified!\n"); + ret = ERR_INTERNAL; + goto done; + } + + subreq = state->handler.send(state, ev, state->sbus_req, state->handler.data); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, _sbus_ifp_invoke_in__out_b_done, req); + ret = EAGAIN; + goto done; + } + + ret = ERR_INTERNAL; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } +} + +static void _sbus_ifp_invoke_in__out_b_done(struct tevent_req *subreq) +{ + struct _sbus_ifp_invoke_in__out_b_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct _sbus_ifp_invoke_in__out_b_state); + + ret = state->handler.recv(state, subreq, &state->out.arg0); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + ret = _sbus_ifp_invoker_write_b(state->write_iterator, &state->out); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +struct _sbus_ifp_invoke_in__out_ifp_extra_state { + struct _sbus_ifp_invoker_args_ifp_extra out; + struct { + enum sbus_handler_type type; + void *data; + errno_t (*sync)(TALLOC_CTX *, struct sbus_request *, void *, hash_table_t **); + struct tevent_req * (*send)(TALLOC_CTX *, struct tevent_context *, struct sbus_request *, void *); + errno_t (*recv)(TALLOC_CTX *, struct tevent_req *, hash_table_t **); + } handler; + + struct sbus_request *sbus_req; + DBusMessageIter *read_iterator; + DBusMessageIter *write_iterator; +}; + +static void +_sbus_ifp_invoke_in__out_ifp_extra_step + (struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *private_data); + +static void +_sbus_ifp_invoke_in__out_ifp_extra_done + (struct tevent_req *subreq); + +struct tevent_req * +_sbus_ifp_invoke_in__out_ifp_extra_send + (TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + sbus_invoker_keygen keygen, + const struct sbus_handler *handler, + DBusMessageIter *read_iterator, + DBusMessageIter *write_iterator, + const char **_key) +{ + struct _sbus_ifp_invoke_in__out_ifp_extra_state *state; + struct tevent_req *req; + const char *key; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct _sbus_ifp_invoke_in__out_ifp_extra_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + state->handler.type = handler->type; + state->handler.data = handler->data; + state->handler.sync = handler->sync; + state->handler.send = handler->async_send; + state->handler.recv = handler->async_recv; + + state->sbus_req = sbus_req; + state->read_iterator = read_iterator; + state->write_iterator = write_iterator; + + ret = sbus_invoker_schedule(state, ev, _sbus_ifp_invoke_in__out_ifp_extra_step, req); + if (ret != EOK) { + goto done; + } + + ret = sbus_request_key(state, keygen, sbus_req, NULL, &key); + if (ret != EOK) { + goto done; + } + + if (_key != NULL) { + *_key = talloc_steal(mem_ctx, key); + } + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void _sbus_ifp_invoke_in__out_ifp_extra_step + (struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *private_data) +{ + struct _sbus_ifp_invoke_in__out_ifp_extra_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = talloc_get_type(private_data, struct tevent_req); + state = tevent_req_data(req, struct _sbus_ifp_invoke_in__out_ifp_extra_state); + + switch (state->handler.type) { + case SBUS_HANDLER_SYNC: + if (state->handler.sync == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: sync handler is not specified!\n"); + ret = ERR_INTERNAL; + goto done; + } + + ret = state->handler.sync(state, state->sbus_req, state->handler.data, &state->out.arg0); + if (ret != EOK) { + goto done; + } + + ret = _sbus_ifp_invoker_write_ifp_extra(state->write_iterator, &state->out); + goto done; + case SBUS_HANDLER_ASYNC: + if (state->handler.send == NULL || state->handler.recv == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: async handler is not specified!\n"); + ret = ERR_INTERNAL; + goto done; + } + + subreq = state->handler.send(state, ev, state->sbus_req, state->handler.data); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, _sbus_ifp_invoke_in__out_ifp_extra_done, req); + ret = EAGAIN; + goto done; + } + + ret = ERR_INTERNAL; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } +} + +static void _sbus_ifp_invoke_in__out_ifp_extra_done(struct tevent_req *subreq) +{ + struct _sbus_ifp_invoke_in__out_ifp_extra_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct _sbus_ifp_invoke_in__out_ifp_extra_state); + + ret = state->handler.recv(state, subreq, &state->out.arg0); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + ret = _sbus_ifp_invoker_write_ifp_extra(state->write_iterator, &state->out); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +struct _sbus_ifp_invoke_in__out_o_state { + struct _sbus_ifp_invoker_args_o out; + struct { + enum sbus_handler_type type; + void *data; + errno_t (*sync)(TALLOC_CTX *, struct sbus_request *, void *, const char **); + struct tevent_req * (*send)(TALLOC_CTX *, struct tevent_context *, struct sbus_request *, void *); + errno_t (*recv)(TALLOC_CTX *, struct tevent_req *, const char **); + } handler; + + struct sbus_request *sbus_req; + DBusMessageIter *read_iterator; + DBusMessageIter *write_iterator; +}; + +static void +_sbus_ifp_invoke_in__out_o_step + (struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *private_data); + +static void +_sbus_ifp_invoke_in__out_o_done + (struct tevent_req *subreq); + +struct tevent_req * +_sbus_ifp_invoke_in__out_o_send + (TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + sbus_invoker_keygen keygen, + const struct sbus_handler *handler, + DBusMessageIter *read_iterator, + DBusMessageIter *write_iterator, + const char **_key) +{ + struct _sbus_ifp_invoke_in__out_o_state *state; + struct tevent_req *req; + const char *key; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct _sbus_ifp_invoke_in__out_o_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + state->handler.type = handler->type; + state->handler.data = handler->data; + state->handler.sync = handler->sync; + state->handler.send = handler->async_send; + state->handler.recv = handler->async_recv; + + state->sbus_req = sbus_req; + state->read_iterator = read_iterator; + state->write_iterator = write_iterator; + + ret = sbus_invoker_schedule(state, ev, _sbus_ifp_invoke_in__out_o_step, req); + if (ret != EOK) { + goto done; + } + + ret = sbus_request_key(state, keygen, sbus_req, NULL, &key); + if (ret != EOK) { + goto done; + } + + if (_key != NULL) { + *_key = talloc_steal(mem_ctx, key); + } + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void _sbus_ifp_invoke_in__out_o_step + (struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *private_data) +{ + struct _sbus_ifp_invoke_in__out_o_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = talloc_get_type(private_data, struct tevent_req); + state = tevent_req_data(req, struct _sbus_ifp_invoke_in__out_o_state); + + switch (state->handler.type) { + case SBUS_HANDLER_SYNC: + if (state->handler.sync == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: sync handler is not specified!\n"); + ret = ERR_INTERNAL; + goto done; + } + + ret = state->handler.sync(state, state->sbus_req, state->handler.data, &state->out.arg0); + if (ret != EOK) { + goto done; + } + + ret = _sbus_ifp_invoker_write_o(state->write_iterator, &state->out); + goto done; + case SBUS_HANDLER_ASYNC: + if (state->handler.send == NULL || state->handler.recv == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: async handler is not specified!\n"); + ret = ERR_INTERNAL; + goto done; + } + + subreq = state->handler.send(state, ev, state->sbus_req, state->handler.data); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, _sbus_ifp_invoke_in__out_o_done, req); + ret = EAGAIN; + goto done; + } + + ret = ERR_INTERNAL; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } +} + +static void _sbus_ifp_invoke_in__out_o_done(struct tevent_req *subreq) +{ + struct _sbus_ifp_invoke_in__out_o_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct _sbus_ifp_invoke_in__out_o_state); + + ret = state->handler.recv(state, subreq, &state->out.arg0); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + ret = _sbus_ifp_invoker_write_o(state->write_iterator, &state->out); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +struct _sbus_ifp_invoke_in__out_s_state { + struct _sbus_ifp_invoker_args_s out; + struct { + enum sbus_handler_type type; + void *data; + errno_t (*sync)(TALLOC_CTX *, struct sbus_request *, void *, const char **); + struct tevent_req * (*send)(TALLOC_CTX *, struct tevent_context *, struct sbus_request *, void *); + errno_t (*recv)(TALLOC_CTX *, struct tevent_req *, const char **); + } handler; + + struct sbus_request *sbus_req; + DBusMessageIter *read_iterator; + DBusMessageIter *write_iterator; +}; + +static void +_sbus_ifp_invoke_in__out_s_step + (struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *private_data); + +static void +_sbus_ifp_invoke_in__out_s_done + (struct tevent_req *subreq); + +struct tevent_req * +_sbus_ifp_invoke_in__out_s_send + (TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + sbus_invoker_keygen keygen, + const struct sbus_handler *handler, + DBusMessageIter *read_iterator, + DBusMessageIter *write_iterator, + const char **_key) +{ + struct _sbus_ifp_invoke_in__out_s_state *state; + struct tevent_req *req; + const char *key; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct _sbus_ifp_invoke_in__out_s_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + state->handler.type = handler->type; + state->handler.data = handler->data; + state->handler.sync = handler->sync; + state->handler.send = handler->async_send; + state->handler.recv = handler->async_recv; + + state->sbus_req = sbus_req; + state->read_iterator = read_iterator; + state->write_iterator = write_iterator; + + ret = sbus_invoker_schedule(state, ev, _sbus_ifp_invoke_in__out_s_step, req); + if (ret != EOK) { + goto done; + } + + ret = sbus_request_key(state, keygen, sbus_req, NULL, &key); + if (ret != EOK) { + goto done; + } + + if (_key != NULL) { + *_key = talloc_steal(mem_ctx, key); + } + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void _sbus_ifp_invoke_in__out_s_step + (struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *private_data) +{ + struct _sbus_ifp_invoke_in__out_s_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = talloc_get_type(private_data, struct tevent_req); + state = tevent_req_data(req, struct _sbus_ifp_invoke_in__out_s_state); + + switch (state->handler.type) { + case SBUS_HANDLER_SYNC: + if (state->handler.sync == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: sync handler is not specified!\n"); + ret = ERR_INTERNAL; + goto done; + } + + ret = state->handler.sync(state, state->sbus_req, state->handler.data, &state->out.arg0); + if (ret != EOK) { + goto done; + } + + ret = _sbus_ifp_invoker_write_s(state->write_iterator, &state->out); + goto done; + case SBUS_HANDLER_ASYNC: + if (state->handler.send == NULL || state->handler.recv == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: async handler is not specified!\n"); + ret = ERR_INTERNAL; + goto done; + } + + subreq = state->handler.send(state, ev, state->sbus_req, state->handler.data); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, _sbus_ifp_invoke_in__out_s_done, req); + ret = EAGAIN; + goto done; + } + + ret = ERR_INTERNAL; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } +} + +static void _sbus_ifp_invoke_in__out_s_done(struct tevent_req *subreq) +{ + struct _sbus_ifp_invoke_in__out_s_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct _sbus_ifp_invoke_in__out_s_state); + + ret = state->handler.recv(state, subreq, &state->out.arg0); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + ret = _sbus_ifp_invoker_write_s(state->write_iterator, &state->out); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +struct _sbus_ifp_invoke_in__out_u_state { + struct _sbus_ifp_invoker_args_u out; + struct { + enum sbus_handler_type type; + void *data; + errno_t (*sync)(TALLOC_CTX *, struct sbus_request *, void *, uint32_t*); + struct tevent_req * (*send)(TALLOC_CTX *, struct tevent_context *, struct sbus_request *, void *); + errno_t (*recv)(TALLOC_CTX *, struct tevent_req *, uint32_t*); + } handler; + + struct sbus_request *sbus_req; + DBusMessageIter *read_iterator; + DBusMessageIter *write_iterator; +}; + +static void +_sbus_ifp_invoke_in__out_u_step + (struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *private_data); + +static void +_sbus_ifp_invoke_in__out_u_done + (struct tevent_req *subreq); + +struct tevent_req * +_sbus_ifp_invoke_in__out_u_send + (TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + sbus_invoker_keygen keygen, + const struct sbus_handler *handler, + DBusMessageIter *read_iterator, + DBusMessageIter *write_iterator, + const char **_key) +{ + struct _sbus_ifp_invoke_in__out_u_state *state; + struct tevent_req *req; + const char *key; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct _sbus_ifp_invoke_in__out_u_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + state->handler.type = handler->type; + state->handler.data = handler->data; + state->handler.sync = handler->sync; + state->handler.send = handler->async_send; + state->handler.recv = handler->async_recv; + + state->sbus_req = sbus_req; + state->read_iterator = read_iterator; + state->write_iterator = write_iterator; + + ret = sbus_invoker_schedule(state, ev, _sbus_ifp_invoke_in__out_u_step, req); + if (ret != EOK) { + goto done; + } + + ret = sbus_request_key(state, keygen, sbus_req, NULL, &key); + if (ret != EOK) { + goto done; + } + + if (_key != NULL) { + *_key = talloc_steal(mem_ctx, key); + } + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void _sbus_ifp_invoke_in__out_u_step + (struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *private_data) +{ + struct _sbus_ifp_invoke_in__out_u_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = talloc_get_type(private_data, struct tevent_req); + state = tevent_req_data(req, struct _sbus_ifp_invoke_in__out_u_state); + + switch (state->handler.type) { + case SBUS_HANDLER_SYNC: + if (state->handler.sync == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: sync handler is not specified!\n"); + ret = ERR_INTERNAL; + goto done; + } + + ret = state->handler.sync(state, state->sbus_req, state->handler.data, &state->out.arg0); + if (ret != EOK) { + goto done; + } + + ret = _sbus_ifp_invoker_write_u(state->write_iterator, &state->out); + goto done; + case SBUS_HANDLER_ASYNC: + if (state->handler.send == NULL || state->handler.recv == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: async handler is not specified!\n"); + ret = ERR_INTERNAL; + goto done; + } + + subreq = state->handler.send(state, ev, state->sbus_req, state->handler.data); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, _sbus_ifp_invoke_in__out_u_done, req); + ret = EAGAIN; + goto done; + } + + ret = ERR_INTERNAL; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } +} + +static void _sbus_ifp_invoke_in__out_u_done(struct tevent_req *subreq) +{ + struct _sbus_ifp_invoke_in__out_u_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct _sbus_ifp_invoke_in__out_u_state); + + ret = state->handler.recv(state, subreq, &state->out.arg0); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + ret = _sbus_ifp_invoker_write_u(state->write_iterator, &state->out); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +struct _sbus_ifp_invoke_in_s_out_ao_state { + struct _sbus_ifp_invoker_args_s *in; + struct _sbus_ifp_invoker_args_ao out; + struct { + enum sbus_handler_type type; + void *data; + errno_t (*sync)(TALLOC_CTX *, struct sbus_request *, void *, const char *, const char ***); + struct tevent_req * (*send)(TALLOC_CTX *, struct tevent_context *, struct sbus_request *, void *, const char *); + errno_t (*recv)(TALLOC_CTX *, struct tevent_req *, const char ***); + } handler; + + struct sbus_request *sbus_req; + DBusMessageIter *read_iterator; + DBusMessageIter *write_iterator; +}; + +static void +_sbus_ifp_invoke_in_s_out_ao_step + (struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *private_data); + +static void +_sbus_ifp_invoke_in_s_out_ao_done + (struct tevent_req *subreq); + +struct tevent_req * +_sbus_ifp_invoke_in_s_out_ao_send + (TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + sbus_invoker_keygen keygen, + const struct sbus_handler *handler, + DBusMessageIter *read_iterator, + DBusMessageIter *write_iterator, + const char **_key) +{ + struct _sbus_ifp_invoke_in_s_out_ao_state *state; + struct tevent_req *req; + const char *key; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct _sbus_ifp_invoke_in_s_out_ao_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + state->handler.type = handler->type; + state->handler.data = handler->data; + state->handler.sync = handler->sync; + state->handler.send = handler->async_send; + state->handler.recv = handler->async_recv; + + state->sbus_req = sbus_req; + state->read_iterator = read_iterator; + state->write_iterator = write_iterator; + + state->in = talloc_zero(state, struct _sbus_ifp_invoker_args_s); + if (state->in == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to allocate space for input parameters!\n"); + ret = ENOMEM; + goto done; + } + + ret = _sbus_ifp_invoker_read_s(state, read_iterator, state->in); + if (ret != EOK) { + goto done; + } + + ret = sbus_invoker_schedule(state, ev, _sbus_ifp_invoke_in_s_out_ao_step, req); + if (ret != EOK) { + goto done; + } + + ret = sbus_request_key(state, keygen, sbus_req, state->in, &key); + if (ret != EOK) { + goto done; + } + + if (_key != NULL) { + *_key = talloc_steal(mem_ctx, key); + } + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void _sbus_ifp_invoke_in_s_out_ao_step + (struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *private_data) +{ + struct _sbus_ifp_invoke_in_s_out_ao_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = talloc_get_type(private_data, struct tevent_req); + state = tevent_req_data(req, struct _sbus_ifp_invoke_in_s_out_ao_state); + + switch (state->handler.type) { + case SBUS_HANDLER_SYNC: + if (state->handler.sync == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: sync handler is not specified!\n"); + ret = ERR_INTERNAL; + goto done; + } + + ret = state->handler.sync(state, state->sbus_req, state->handler.data, state->in->arg0, &state->out.arg0); + if (ret != EOK) { + goto done; + } + + ret = _sbus_ifp_invoker_write_ao(state->write_iterator, &state->out); + goto done; + case SBUS_HANDLER_ASYNC: + if (state->handler.send == NULL || state->handler.recv == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: async handler is not specified!\n"); + ret = ERR_INTERNAL; + goto done; + } + + subreq = state->handler.send(state, ev, state->sbus_req, state->handler.data, state->in->arg0); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, _sbus_ifp_invoke_in_s_out_ao_done, req); + ret = EAGAIN; + goto done; + } + + ret = ERR_INTERNAL; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } +} + +static void _sbus_ifp_invoke_in_s_out_ao_done(struct tevent_req *subreq) +{ + struct _sbus_ifp_invoke_in_s_out_ao_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct _sbus_ifp_invoke_in_s_out_ao_state); + + ret = state->handler.recv(state, subreq, &state->out.arg0); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + ret = _sbus_ifp_invoker_write_ao(state->write_iterator, &state->out); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +struct _sbus_ifp_invoke_in_s_out_as_state { + struct _sbus_ifp_invoker_args_s *in; + struct _sbus_ifp_invoker_args_as out; + struct { + enum sbus_handler_type type; + void *data; + errno_t (*sync)(TALLOC_CTX *, struct sbus_request *, void *, const char *, const char ***); + struct tevent_req * (*send)(TALLOC_CTX *, struct tevent_context *, struct sbus_request *, void *, const char *); + errno_t (*recv)(TALLOC_CTX *, struct tevent_req *, const char ***); + } handler; + + struct sbus_request *sbus_req; + DBusMessageIter *read_iterator; + DBusMessageIter *write_iterator; +}; + +static void +_sbus_ifp_invoke_in_s_out_as_step + (struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *private_data); + +static void +_sbus_ifp_invoke_in_s_out_as_done + (struct tevent_req *subreq); + +struct tevent_req * +_sbus_ifp_invoke_in_s_out_as_send + (TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + sbus_invoker_keygen keygen, + const struct sbus_handler *handler, + DBusMessageIter *read_iterator, + DBusMessageIter *write_iterator, + const char **_key) +{ + struct _sbus_ifp_invoke_in_s_out_as_state *state; + struct tevent_req *req; + const char *key; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct _sbus_ifp_invoke_in_s_out_as_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + state->handler.type = handler->type; + state->handler.data = handler->data; + state->handler.sync = handler->sync; + state->handler.send = handler->async_send; + state->handler.recv = handler->async_recv; + + state->sbus_req = sbus_req; + state->read_iterator = read_iterator; + state->write_iterator = write_iterator; + + state->in = talloc_zero(state, struct _sbus_ifp_invoker_args_s); + if (state->in == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to allocate space for input parameters!\n"); + ret = ENOMEM; + goto done; + } + + ret = _sbus_ifp_invoker_read_s(state, read_iterator, state->in); + if (ret != EOK) { + goto done; + } + + ret = sbus_invoker_schedule(state, ev, _sbus_ifp_invoke_in_s_out_as_step, req); + if (ret != EOK) { + goto done; + } + + ret = sbus_request_key(state, keygen, sbus_req, state->in, &key); + if (ret != EOK) { + goto done; + } + + if (_key != NULL) { + *_key = talloc_steal(mem_ctx, key); + } + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void _sbus_ifp_invoke_in_s_out_as_step + (struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *private_data) +{ + struct _sbus_ifp_invoke_in_s_out_as_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = talloc_get_type(private_data, struct tevent_req); + state = tevent_req_data(req, struct _sbus_ifp_invoke_in_s_out_as_state); + + switch (state->handler.type) { + case SBUS_HANDLER_SYNC: + if (state->handler.sync == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: sync handler is not specified!\n"); + ret = ERR_INTERNAL; + goto done; + } + + ret = state->handler.sync(state, state->sbus_req, state->handler.data, state->in->arg0, &state->out.arg0); + if (ret != EOK) { + goto done; + } + + ret = _sbus_ifp_invoker_write_as(state->write_iterator, &state->out); + goto done; + case SBUS_HANDLER_ASYNC: + if (state->handler.send == NULL || state->handler.recv == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: async handler is not specified!\n"); + ret = ERR_INTERNAL; + goto done; + } + + subreq = state->handler.send(state, ev, state->sbus_req, state->handler.data, state->in->arg0); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, _sbus_ifp_invoke_in_s_out_as_done, req); + ret = EAGAIN; + goto done; + } + + ret = ERR_INTERNAL; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } +} + +static void _sbus_ifp_invoke_in_s_out_as_done(struct tevent_req *subreq) +{ + struct _sbus_ifp_invoke_in_s_out_as_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct _sbus_ifp_invoke_in_s_out_as_state); + + ret = state->handler.recv(state, subreq, &state->out.arg0); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + ret = _sbus_ifp_invoker_write_as(state->write_iterator, &state->out); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +struct _sbus_ifp_invoke_in_s_out_o_state { + struct _sbus_ifp_invoker_args_s *in; + struct _sbus_ifp_invoker_args_o out; + struct { + enum sbus_handler_type type; + void *data; + errno_t (*sync)(TALLOC_CTX *, struct sbus_request *, void *, const char *, const char **); + struct tevent_req * (*send)(TALLOC_CTX *, struct tevent_context *, struct sbus_request *, void *, const char *); + errno_t (*recv)(TALLOC_CTX *, struct tevent_req *, const char **); + } handler; + + struct sbus_request *sbus_req; + DBusMessageIter *read_iterator; + DBusMessageIter *write_iterator; +}; + +static void +_sbus_ifp_invoke_in_s_out_o_step + (struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *private_data); + +static void +_sbus_ifp_invoke_in_s_out_o_done + (struct tevent_req *subreq); + +struct tevent_req * +_sbus_ifp_invoke_in_s_out_o_send + (TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + sbus_invoker_keygen keygen, + const struct sbus_handler *handler, + DBusMessageIter *read_iterator, + DBusMessageIter *write_iterator, + const char **_key) +{ + struct _sbus_ifp_invoke_in_s_out_o_state *state; + struct tevent_req *req; + const char *key; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct _sbus_ifp_invoke_in_s_out_o_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + state->handler.type = handler->type; + state->handler.data = handler->data; + state->handler.sync = handler->sync; + state->handler.send = handler->async_send; + state->handler.recv = handler->async_recv; + + state->sbus_req = sbus_req; + state->read_iterator = read_iterator; + state->write_iterator = write_iterator; + + state->in = talloc_zero(state, struct _sbus_ifp_invoker_args_s); + if (state->in == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to allocate space for input parameters!\n"); + ret = ENOMEM; + goto done; + } + + ret = _sbus_ifp_invoker_read_s(state, read_iterator, state->in); + if (ret != EOK) { + goto done; + } + + ret = sbus_invoker_schedule(state, ev, _sbus_ifp_invoke_in_s_out_o_step, req); + if (ret != EOK) { + goto done; + } + + ret = sbus_request_key(state, keygen, sbus_req, state->in, &key); + if (ret != EOK) { + goto done; + } + + if (_key != NULL) { + *_key = talloc_steal(mem_ctx, key); + } + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void _sbus_ifp_invoke_in_s_out_o_step + (struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *private_data) +{ + struct _sbus_ifp_invoke_in_s_out_o_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = talloc_get_type(private_data, struct tevent_req); + state = tevent_req_data(req, struct _sbus_ifp_invoke_in_s_out_o_state); + + switch (state->handler.type) { + case SBUS_HANDLER_SYNC: + if (state->handler.sync == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: sync handler is not specified!\n"); + ret = ERR_INTERNAL; + goto done; + } + + ret = state->handler.sync(state, state->sbus_req, state->handler.data, state->in->arg0, &state->out.arg0); + if (ret != EOK) { + goto done; + } + + ret = _sbus_ifp_invoker_write_o(state->write_iterator, &state->out); + goto done; + case SBUS_HANDLER_ASYNC: + if (state->handler.send == NULL || state->handler.recv == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: async handler is not specified!\n"); + ret = ERR_INTERNAL; + goto done; + } + + subreq = state->handler.send(state, ev, state->sbus_req, state->handler.data, state->in->arg0); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, _sbus_ifp_invoke_in_s_out_o_done, req); + ret = EAGAIN; + goto done; + } + + ret = ERR_INTERNAL; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } +} + +static void _sbus_ifp_invoke_in_s_out_o_done(struct tevent_req *subreq) +{ + struct _sbus_ifp_invoke_in_s_out_o_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct _sbus_ifp_invoke_in_s_out_o_state); + + ret = state->handler.recv(state, subreq, &state->out.arg0); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + ret = _sbus_ifp_invoker_write_o(state->write_iterator, &state->out); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +struct _sbus_ifp_invoke_in_s_out_s_state { + struct _sbus_ifp_invoker_args_s *in; + struct _sbus_ifp_invoker_args_s out; + struct { + enum sbus_handler_type type; + void *data; + errno_t (*sync)(TALLOC_CTX *, struct sbus_request *, void *, const char *, const char **); + struct tevent_req * (*send)(TALLOC_CTX *, struct tevent_context *, struct sbus_request *, void *, const char *); + errno_t (*recv)(TALLOC_CTX *, struct tevent_req *, const char **); + } handler; + + struct sbus_request *sbus_req; + DBusMessageIter *read_iterator; + DBusMessageIter *write_iterator; +}; + +static void +_sbus_ifp_invoke_in_s_out_s_step + (struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *private_data); + +static void +_sbus_ifp_invoke_in_s_out_s_done + (struct tevent_req *subreq); + +struct tevent_req * +_sbus_ifp_invoke_in_s_out_s_send + (TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + sbus_invoker_keygen keygen, + const struct sbus_handler *handler, + DBusMessageIter *read_iterator, + DBusMessageIter *write_iterator, + const char **_key) +{ + struct _sbus_ifp_invoke_in_s_out_s_state *state; + struct tevent_req *req; + const char *key; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct _sbus_ifp_invoke_in_s_out_s_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + state->handler.type = handler->type; + state->handler.data = handler->data; + state->handler.sync = handler->sync; + state->handler.send = handler->async_send; + state->handler.recv = handler->async_recv; + + state->sbus_req = sbus_req; + state->read_iterator = read_iterator; + state->write_iterator = write_iterator; + + state->in = talloc_zero(state, struct _sbus_ifp_invoker_args_s); + if (state->in == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to allocate space for input parameters!\n"); + ret = ENOMEM; + goto done; + } + + ret = _sbus_ifp_invoker_read_s(state, read_iterator, state->in); + if (ret != EOK) { + goto done; + } + + ret = sbus_invoker_schedule(state, ev, _sbus_ifp_invoke_in_s_out_s_step, req); + if (ret != EOK) { + goto done; + } + + ret = sbus_request_key(state, keygen, sbus_req, state->in, &key); + if (ret != EOK) { + goto done; + } + + if (_key != NULL) { + *_key = talloc_steal(mem_ctx, key); + } + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void _sbus_ifp_invoke_in_s_out_s_step + (struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *private_data) +{ + struct _sbus_ifp_invoke_in_s_out_s_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = talloc_get_type(private_data, struct tevent_req); + state = tevent_req_data(req, struct _sbus_ifp_invoke_in_s_out_s_state); + + switch (state->handler.type) { + case SBUS_HANDLER_SYNC: + if (state->handler.sync == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: sync handler is not specified!\n"); + ret = ERR_INTERNAL; + goto done; + } + + ret = state->handler.sync(state, state->sbus_req, state->handler.data, state->in->arg0, &state->out.arg0); + if (ret != EOK) { + goto done; + } + + ret = _sbus_ifp_invoker_write_s(state->write_iterator, &state->out); + goto done; + case SBUS_HANDLER_ASYNC: + if (state->handler.send == NULL || state->handler.recv == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: async handler is not specified!\n"); + ret = ERR_INTERNAL; + goto done; + } + + subreq = state->handler.send(state, ev, state->sbus_req, state->handler.data, state->in->arg0); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, _sbus_ifp_invoke_in_s_out_s_done, req); + ret = EAGAIN; + goto done; + } + + ret = ERR_INTERNAL; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } +} + +static void _sbus_ifp_invoke_in_s_out_s_done(struct tevent_req *subreq) +{ + struct _sbus_ifp_invoke_in_s_out_s_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct _sbus_ifp_invoke_in_s_out_s_state); + + ret = state->handler.recv(state, subreq, &state->out.arg0); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + ret = _sbus_ifp_invoker_write_s(state->write_iterator, &state->out); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +struct _sbus_ifp_invoke_in_sas_out_raw_state { + struct _sbus_ifp_invoker_args_sas *in; + struct { + enum sbus_handler_type type; + void *data; + errno_t (*sync)(TALLOC_CTX *, struct sbus_request *, void *, const char *, const char **, DBusMessageIter *); + struct tevent_req * (*send)(TALLOC_CTX *, struct tevent_context *, struct sbus_request *, void *, const char *, const char **, DBusMessageIter *); + errno_t (*recv)(TALLOC_CTX *, struct tevent_req *); + } handler; + + struct sbus_request *sbus_req; + DBusMessageIter *read_iterator; + DBusMessageIter *write_iterator; +}; + +static void +_sbus_ifp_invoke_in_sas_out_raw_step + (struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *private_data); + +static void +_sbus_ifp_invoke_in_sas_out_raw_done + (struct tevent_req *subreq); + +struct tevent_req * +_sbus_ifp_invoke_in_sas_out_raw_send + (TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + sbus_invoker_keygen keygen, + const struct sbus_handler *handler, + DBusMessageIter *read_iterator, + DBusMessageIter *write_iterator, + const char **_key) +{ + struct _sbus_ifp_invoke_in_sas_out_raw_state *state; + struct tevent_req *req; + const char *key; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct _sbus_ifp_invoke_in_sas_out_raw_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + state->handler.type = handler->type; + state->handler.data = handler->data; + state->handler.sync = handler->sync; + state->handler.send = handler->async_send; + state->handler.recv = handler->async_recv; + + state->sbus_req = sbus_req; + state->read_iterator = read_iterator; + state->write_iterator = write_iterator; + + state->in = talloc_zero(state, struct _sbus_ifp_invoker_args_sas); + if (state->in == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to allocate space for input parameters!\n"); + ret = ENOMEM; + goto done; + } + + ret = _sbus_ifp_invoker_read_sas(state, read_iterator, state->in); + if (ret != EOK) { + goto done; + } + + ret = sbus_invoker_schedule(state, ev, _sbus_ifp_invoke_in_sas_out_raw_step, req); + if (ret != EOK) { + goto done; + } + + ret = sbus_request_key(state, keygen, sbus_req, state->in, &key); + if (ret != EOK) { + goto done; + } + + if (_key != NULL) { + *_key = talloc_steal(mem_ctx, key); + } + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void _sbus_ifp_invoke_in_sas_out_raw_step + (struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *private_data) +{ + struct _sbus_ifp_invoke_in_sas_out_raw_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = talloc_get_type(private_data, struct tevent_req); + state = tevent_req_data(req, struct _sbus_ifp_invoke_in_sas_out_raw_state); + + switch (state->handler.type) { + case SBUS_HANDLER_SYNC: + if (state->handler.sync == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: sync handler is not specified!\n"); + ret = ERR_INTERNAL; + goto done; + } + + ret = state->handler.sync(state, state->sbus_req, state->handler.data, state->in->arg0, state->in->arg1, state->write_iterator); + if (ret != EOK) { + goto done; + } + + goto done; + case SBUS_HANDLER_ASYNC: + if (state->handler.send == NULL || state->handler.recv == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: async handler is not specified!\n"); + ret = ERR_INTERNAL; + goto done; + } + + subreq = state->handler.send(state, ev, state->sbus_req, state->handler.data, state->in->arg0, state->in->arg1, state->write_iterator); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, _sbus_ifp_invoke_in_sas_out_raw_done, req); + ret = EAGAIN; + goto done; + } + + ret = ERR_INTERNAL; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } +} + +static void _sbus_ifp_invoke_in_sas_out_raw_done(struct tevent_req *subreq) +{ + struct _sbus_ifp_invoke_in_sas_out_raw_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct _sbus_ifp_invoke_in_sas_out_raw_state); + + ret = state->handler.recv(state, subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +struct _sbus_ifp_invoke_in_ss_out_o_state { + struct _sbus_ifp_invoker_args_ss *in; + struct _sbus_ifp_invoker_args_o out; + struct { + enum sbus_handler_type type; + void *data; + errno_t (*sync)(TALLOC_CTX *, struct sbus_request *, void *, const char *, const char *, const char **); + struct tevent_req * (*send)(TALLOC_CTX *, struct tevent_context *, struct sbus_request *, void *, const char *, const char *); + errno_t (*recv)(TALLOC_CTX *, struct tevent_req *, const char **); + } handler; + + struct sbus_request *sbus_req; + DBusMessageIter *read_iterator; + DBusMessageIter *write_iterator; +}; + +static void +_sbus_ifp_invoke_in_ss_out_o_step + (struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *private_data); + +static void +_sbus_ifp_invoke_in_ss_out_o_done + (struct tevent_req *subreq); + +struct tevent_req * +_sbus_ifp_invoke_in_ss_out_o_send + (TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + sbus_invoker_keygen keygen, + const struct sbus_handler *handler, + DBusMessageIter *read_iterator, + DBusMessageIter *write_iterator, + const char **_key) +{ + struct _sbus_ifp_invoke_in_ss_out_o_state *state; + struct tevent_req *req; + const char *key; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct _sbus_ifp_invoke_in_ss_out_o_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + state->handler.type = handler->type; + state->handler.data = handler->data; + state->handler.sync = handler->sync; + state->handler.send = handler->async_send; + state->handler.recv = handler->async_recv; + + state->sbus_req = sbus_req; + state->read_iterator = read_iterator; + state->write_iterator = write_iterator; + + state->in = talloc_zero(state, struct _sbus_ifp_invoker_args_ss); + if (state->in == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to allocate space for input parameters!\n"); + ret = ENOMEM; + goto done; + } + + ret = _sbus_ifp_invoker_read_ss(state, read_iterator, state->in); + if (ret != EOK) { + goto done; + } + + ret = sbus_invoker_schedule(state, ev, _sbus_ifp_invoke_in_ss_out_o_step, req); + if (ret != EOK) { + goto done; + } + + ret = sbus_request_key(state, keygen, sbus_req, state->in, &key); + if (ret != EOK) { + goto done; + } + + if (_key != NULL) { + *_key = talloc_steal(mem_ctx, key); + } + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void _sbus_ifp_invoke_in_ss_out_o_step + (struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *private_data) +{ + struct _sbus_ifp_invoke_in_ss_out_o_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = talloc_get_type(private_data, struct tevent_req); + state = tevent_req_data(req, struct _sbus_ifp_invoke_in_ss_out_o_state); + + switch (state->handler.type) { + case SBUS_HANDLER_SYNC: + if (state->handler.sync == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: sync handler is not specified!\n"); + ret = ERR_INTERNAL; + goto done; + } + + ret = state->handler.sync(state, state->sbus_req, state->handler.data, state->in->arg0, state->in->arg1, &state->out.arg0); + if (ret != EOK) { + goto done; + } + + ret = _sbus_ifp_invoker_write_o(state->write_iterator, &state->out); + goto done; + case SBUS_HANDLER_ASYNC: + if (state->handler.send == NULL || state->handler.recv == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: async handler is not specified!\n"); + ret = ERR_INTERNAL; + goto done; + } + + subreq = state->handler.send(state, ev, state->sbus_req, state->handler.data, state->in->arg0, state->in->arg1); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, _sbus_ifp_invoke_in_ss_out_o_done, req); + ret = EAGAIN; + goto done; + } + + ret = ERR_INTERNAL; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } +} + +static void _sbus_ifp_invoke_in_ss_out_o_done(struct tevent_req *subreq) +{ + struct _sbus_ifp_invoke_in_ss_out_o_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct _sbus_ifp_invoke_in_ss_out_o_state); + + ret = state->handler.recv(state, subreq, &state->out.arg0); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + ret = _sbus_ifp_invoker_write_o(state->write_iterator, &state->out); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +struct _sbus_ifp_invoke_in_ssu_out_ao_state { + struct _sbus_ifp_invoker_args_ssu *in; + struct _sbus_ifp_invoker_args_ao out; + struct { + enum sbus_handler_type type; + void *data; + errno_t (*sync)(TALLOC_CTX *, struct sbus_request *, void *, const char *, const char *, uint32_t, const char ***); + struct tevent_req * (*send)(TALLOC_CTX *, struct tevent_context *, struct sbus_request *, void *, const char *, const char *, uint32_t); + errno_t (*recv)(TALLOC_CTX *, struct tevent_req *, const char ***); + } handler; + + struct sbus_request *sbus_req; + DBusMessageIter *read_iterator; + DBusMessageIter *write_iterator; +}; + +static void +_sbus_ifp_invoke_in_ssu_out_ao_step + (struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *private_data); + +static void +_sbus_ifp_invoke_in_ssu_out_ao_done + (struct tevent_req *subreq); + +struct tevent_req * +_sbus_ifp_invoke_in_ssu_out_ao_send + (TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + sbus_invoker_keygen keygen, + const struct sbus_handler *handler, + DBusMessageIter *read_iterator, + DBusMessageIter *write_iterator, + const char **_key) +{ + struct _sbus_ifp_invoke_in_ssu_out_ao_state *state; + struct tevent_req *req; + const char *key; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct _sbus_ifp_invoke_in_ssu_out_ao_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + state->handler.type = handler->type; + state->handler.data = handler->data; + state->handler.sync = handler->sync; + state->handler.send = handler->async_send; + state->handler.recv = handler->async_recv; + + state->sbus_req = sbus_req; + state->read_iterator = read_iterator; + state->write_iterator = write_iterator; + + state->in = talloc_zero(state, struct _sbus_ifp_invoker_args_ssu); + if (state->in == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to allocate space for input parameters!\n"); + ret = ENOMEM; + goto done; + } + + ret = _sbus_ifp_invoker_read_ssu(state, read_iterator, state->in); + if (ret != EOK) { + goto done; + } + + ret = sbus_invoker_schedule(state, ev, _sbus_ifp_invoke_in_ssu_out_ao_step, req); + if (ret != EOK) { + goto done; + } + + ret = sbus_request_key(state, keygen, sbus_req, state->in, &key); + if (ret != EOK) { + goto done; + } + + if (_key != NULL) { + *_key = talloc_steal(mem_ctx, key); + } + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void _sbus_ifp_invoke_in_ssu_out_ao_step + (struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *private_data) +{ + struct _sbus_ifp_invoke_in_ssu_out_ao_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = talloc_get_type(private_data, struct tevent_req); + state = tevent_req_data(req, struct _sbus_ifp_invoke_in_ssu_out_ao_state); + + switch (state->handler.type) { + case SBUS_HANDLER_SYNC: + if (state->handler.sync == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: sync handler is not specified!\n"); + ret = ERR_INTERNAL; + goto done; + } + + ret = state->handler.sync(state, state->sbus_req, state->handler.data, state->in->arg0, state->in->arg1, state->in->arg2, &state->out.arg0); + if (ret != EOK) { + goto done; + } + + ret = _sbus_ifp_invoker_write_ao(state->write_iterator, &state->out); + goto done; + case SBUS_HANDLER_ASYNC: + if (state->handler.send == NULL || state->handler.recv == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: async handler is not specified!\n"); + ret = ERR_INTERNAL; + goto done; + } + + subreq = state->handler.send(state, ev, state->sbus_req, state->handler.data, state->in->arg0, state->in->arg1, state->in->arg2); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, _sbus_ifp_invoke_in_ssu_out_ao_done, req); + ret = EAGAIN; + goto done; + } + + ret = ERR_INTERNAL; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } +} + +static void _sbus_ifp_invoke_in_ssu_out_ao_done(struct tevent_req *subreq) +{ + struct _sbus_ifp_invoke_in_ssu_out_ao_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct _sbus_ifp_invoke_in_ssu_out_ao_state); + + ret = state->handler.recv(state, subreq, &state->out.arg0); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + ret = _sbus_ifp_invoker_write_ao(state->write_iterator, &state->out); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +struct _sbus_ifp_invoke_in_su_out_ao_state { + struct _sbus_ifp_invoker_args_su *in; + struct _sbus_ifp_invoker_args_ao out; + struct { + enum sbus_handler_type type; + void *data; + errno_t (*sync)(TALLOC_CTX *, struct sbus_request *, void *, const char *, uint32_t, const char ***); + struct tevent_req * (*send)(TALLOC_CTX *, struct tevent_context *, struct sbus_request *, void *, const char *, uint32_t); + errno_t (*recv)(TALLOC_CTX *, struct tevent_req *, const char ***); + } handler; + + struct sbus_request *sbus_req; + DBusMessageIter *read_iterator; + DBusMessageIter *write_iterator; +}; + +static void +_sbus_ifp_invoke_in_su_out_ao_step + (struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *private_data); + +static void +_sbus_ifp_invoke_in_su_out_ao_done + (struct tevent_req *subreq); + +struct tevent_req * +_sbus_ifp_invoke_in_su_out_ao_send + (TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + sbus_invoker_keygen keygen, + const struct sbus_handler *handler, + DBusMessageIter *read_iterator, + DBusMessageIter *write_iterator, + const char **_key) +{ + struct _sbus_ifp_invoke_in_su_out_ao_state *state; + struct tevent_req *req; + const char *key; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct _sbus_ifp_invoke_in_su_out_ao_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + state->handler.type = handler->type; + state->handler.data = handler->data; + state->handler.sync = handler->sync; + state->handler.send = handler->async_send; + state->handler.recv = handler->async_recv; + + state->sbus_req = sbus_req; + state->read_iterator = read_iterator; + state->write_iterator = write_iterator; + + state->in = talloc_zero(state, struct _sbus_ifp_invoker_args_su); + if (state->in == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to allocate space for input parameters!\n"); + ret = ENOMEM; + goto done; + } + + ret = _sbus_ifp_invoker_read_su(state, read_iterator, state->in); + if (ret != EOK) { + goto done; + } + + ret = sbus_invoker_schedule(state, ev, _sbus_ifp_invoke_in_su_out_ao_step, req); + if (ret != EOK) { + goto done; + } + + ret = sbus_request_key(state, keygen, sbus_req, state->in, &key); + if (ret != EOK) { + goto done; + } + + if (_key != NULL) { + *_key = talloc_steal(mem_ctx, key); + } + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void _sbus_ifp_invoke_in_su_out_ao_step + (struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *private_data) +{ + struct _sbus_ifp_invoke_in_su_out_ao_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = talloc_get_type(private_data, struct tevent_req); + state = tevent_req_data(req, struct _sbus_ifp_invoke_in_su_out_ao_state); + + switch (state->handler.type) { + case SBUS_HANDLER_SYNC: + if (state->handler.sync == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: sync handler is not specified!\n"); + ret = ERR_INTERNAL; + goto done; + } + + ret = state->handler.sync(state, state->sbus_req, state->handler.data, state->in->arg0, state->in->arg1, &state->out.arg0); + if (ret != EOK) { + goto done; + } + + ret = _sbus_ifp_invoker_write_ao(state->write_iterator, &state->out); + goto done; + case SBUS_HANDLER_ASYNC: + if (state->handler.send == NULL || state->handler.recv == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: async handler is not specified!\n"); + ret = ERR_INTERNAL; + goto done; + } + + subreq = state->handler.send(state, ev, state->sbus_req, state->handler.data, state->in->arg0, state->in->arg1); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, _sbus_ifp_invoke_in_su_out_ao_done, req); + ret = EAGAIN; + goto done; + } + + ret = ERR_INTERNAL; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } +} + +static void _sbus_ifp_invoke_in_su_out_ao_done(struct tevent_req *subreq) +{ + struct _sbus_ifp_invoke_in_su_out_ao_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct _sbus_ifp_invoke_in_su_out_ao_state); + + ret = state->handler.recv(state, subreq, &state->out.arg0); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + ret = _sbus_ifp_invoker_write_ao(state->write_iterator, &state->out); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +struct _sbus_ifp_invoke_in_u_out_o_state { + struct _sbus_ifp_invoker_args_u *in; + struct _sbus_ifp_invoker_args_o out; + struct { + enum sbus_handler_type type; + void *data; + errno_t (*sync)(TALLOC_CTX *, struct sbus_request *, void *, uint32_t, const char **); + struct tevent_req * (*send)(TALLOC_CTX *, struct tevent_context *, struct sbus_request *, void *, uint32_t); + errno_t (*recv)(TALLOC_CTX *, struct tevent_req *, const char **); + } handler; + + struct sbus_request *sbus_req; + DBusMessageIter *read_iterator; + DBusMessageIter *write_iterator; +}; + +static void +_sbus_ifp_invoke_in_u_out_o_step + (struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *private_data); + +static void +_sbus_ifp_invoke_in_u_out_o_done + (struct tevent_req *subreq); + +struct tevent_req * +_sbus_ifp_invoke_in_u_out_o_send + (TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + sbus_invoker_keygen keygen, + const struct sbus_handler *handler, + DBusMessageIter *read_iterator, + DBusMessageIter *write_iterator, + const char **_key) +{ + struct _sbus_ifp_invoke_in_u_out_o_state *state; + struct tevent_req *req; + const char *key; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct _sbus_ifp_invoke_in_u_out_o_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + state->handler.type = handler->type; + state->handler.data = handler->data; + state->handler.sync = handler->sync; + state->handler.send = handler->async_send; + state->handler.recv = handler->async_recv; + + state->sbus_req = sbus_req; + state->read_iterator = read_iterator; + state->write_iterator = write_iterator; + + state->in = talloc_zero(state, struct _sbus_ifp_invoker_args_u); + if (state->in == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to allocate space for input parameters!\n"); + ret = ENOMEM; + goto done; + } + + ret = _sbus_ifp_invoker_read_u(state, read_iterator, state->in); + if (ret != EOK) { + goto done; + } + + ret = sbus_invoker_schedule(state, ev, _sbus_ifp_invoke_in_u_out_o_step, req); + if (ret != EOK) { + goto done; + } + + ret = sbus_request_key(state, keygen, sbus_req, state->in, &key); + if (ret != EOK) { + goto done; + } + + if (_key != NULL) { + *_key = talloc_steal(mem_ctx, key); + } + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void _sbus_ifp_invoke_in_u_out_o_step + (struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *private_data) +{ + struct _sbus_ifp_invoke_in_u_out_o_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = talloc_get_type(private_data, struct tevent_req); + state = tevent_req_data(req, struct _sbus_ifp_invoke_in_u_out_o_state); + + switch (state->handler.type) { + case SBUS_HANDLER_SYNC: + if (state->handler.sync == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: sync handler is not specified!\n"); + ret = ERR_INTERNAL; + goto done; + } + + ret = state->handler.sync(state, state->sbus_req, state->handler.data, state->in->arg0, &state->out.arg0); + if (ret != EOK) { + goto done; + } + + ret = _sbus_ifp_invoker_write_o(state->write_iterator, &state->out); + goto done; + case SBUS_HANDLER_ASYNC: + if (state->handler.send == NULL || state->handler.recv == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: async handler is not specified!\n"); + ret = ERR_INTERNAL; + goto done; + } + + subreq = state->handler.send(state, ev, state->sbus_req, state->handler.data, state->in->arg0); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, _sbus_ifp_invoke_in_u_out_o_done, req); + ret = EAGAIN; + goto done; + } + + ret = ERR_INTERNAL; + +done: + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } +} + +static void _sbus_ifp_invoke_in_u_out_o_done(struct tevent_req *subreq) +{ + struct _sbus_ifp_invoke_in_u_out_o_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct _sbus_ifp_invoke_in_u_out_o_state); + + ret = state->handler.recv(state, subreq, &state->out.arg0); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + ret = _sbus_ifp_invoker_write_o(state->write_iterator, &state->out); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} diff --git a/src/responder/ifp/ifp_iface/sbus_ifp_invokers.h b/src/responder/ifp/ifp_iface/sbus_ifp_invokers.h new file mode 100644 index 0000000..12d8f22 --- /dev/null +++ b/src/responder/ifp/ifp_iface/sbus_ifp_invokers.h @@ -0,0 +1,60 @@ +/* + Generated by sbus code generator + + Copyright (C) 2017 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/>. +*/ + +#ifndef _SBUS_IFP_INVOKERS_H_ +#define _SBUS_IFP_INVOKERS_H_ + +#include <talloc.h> +#include <tevent.h> +#include <dbus/dbus.h> + +#include "sbus/sbus_interface_declarations.h" +#include "sbus/sbus_request.h" + +#define _sbus_ifp_declare_invoker(input, output) \ + struct tevent_req * \ + _sbus_ifp_invoke_in_ ## input ## _out_ ## output ## _send \ + (TALLOC_CTX *mem_ctx, \ + struct tevent_context *ev, \ + struct sbus_request *sbus_req, \ + sbus_invoker_keygen keygen, \ + const struct sbus_handler *handler, \ + DBusMessageIter *read_iterator, \ + DBusMessageIter *write_iterator, \ + const char **_key) + +_sbus_ifp_declare_invoker(, ); +_sbus_ifp_declare_invoker(, ao); +_sbus_ifp_declare_invoker(, as); +_sbus_ifp_declare_invoker(, b); +_sbus_ifp_declare_invoker(, ifp_extra); +_sbus_ifp_declare_invoker(, o); +_sbus_ifp_declare_invoker(, s); +_sbus_ifp_declare_invoker(, u); +_sbus_ifp_declare_invoker(s, ao); +_sbus_ifp_declare_invoker(s, as); +_sbus_ifp_declare_invoker(s, o); +_sbus_ifp_declare_invoker(s, s); +_sbus_ifp_declare_invoker(sas, raw); +_sbus_ifp_declare_invoker(ss, o); +_sbus_ifp_declare_invoker(ssu, ao); +_sbus_ifp_declare_invoker(su, ao); +_sbus_ifp_declare_invoker(u, o); + +#endif /* _SBUS_IFP_INVOKERS_H_ */ diff --git a/src/responder/ifp/ifp_iface/sbus_ifp_keygens.c b/src/responder/ifp/ifp_iface/sbus_ifp_keygens.c new file mode 100644 index 0000000..0f2f4ae --- /dev/null +++ b/src/responder/ifp/ifp_iface/sbus_ifp_keygens.c @@ -0,0 +1,107 @@ +/* + Generated by sbus code generator + + Copyright (C) 2017 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 <inttypes.h> +#include <talloc.h> + +#include "sbus/sbus_request.h" +#include "responder/ifp/ifp_iface/sbus_ifp_arguments.h" +#include "responder/ifp/ifp_iface/sbus_ifp_keygens.h" + +const char * +_sbus_ifp_key_ + (TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req) +{ + if (sbus_req->sender == NULL) { + return talloc_asprintf(mem_ctx, "-:%u:%s.%s:%s", + sbus_req->type, sbus_req->interface, sbus_req->member, sbus_req->path); + } + + return talloc_asprintf(mem_ctx, "%"PRIi64":%u:%s.%s:%s", + sbus_req->sender->uid, sbus_req->type, sbus_req->interface, sbus_req->member, sbus_req->path); +} + +const char * +_sbus_ifp_key_s_0 + (TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct _sbus_ifp_invoker_args_s *args) +{ + if (sbus_req->sender == NULL) { + return talloc_asprintf(mem_ctx, "-:%u:%s.%s:%s:%s", + sbus_req->type, sbus_req->interface, sbus_req->member, + sbus_req->path, args->arg0); + } + + return talloc_asprintf(mem_ctx, "%"PRIi64":%u:%s.%s:%s:%s", + sbus_req->sender->uid, sbus_req->type, sbus_req->interface, sbus_req->member, + sbus_req->path, args->arg0); +} + +const char * +_sbus_ifp_key_ssu_0_1_2 + (TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct _sbus_ifp_invoker_args_ssu *args) +{ + if (sbus_req->sender == NULL) { + return talloc_asprintf(mem_ctx, "-:%u:%s.%s:%s:%s:%s:%" PRIu32 "", + sbus_req->type, sbus_req->interface, sbus_req->member, + sbus_req->path, args->arg0, args->arg1, args->arg2); + } + + return talloc_asprintf(mem_ctx, "%"PRIi64":%u:%s.%s:%s:%s:%s:%" PRIu32 "", + sbus_req->sender->uid, sbus_req->type, sbus_req->interface, sbus_req->member, + sbus_req->path, args->arg0, args->arg1, args->arg2); +} + +const char * +_sbus_ifp_key_su_0_1 + (TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct _sbus_ifp_invoker_args_su *args) +{ + if (sbus_req->sender == NULL) { + return talloc_asprintf(mem_ctx, "-:%u:%s.%s:%s:%s:%" PRIu32 "", + sbus_req->type, sbus_req->interface, sbus_req->member, + sbus_req->path, args->arg0, args->arg1); + } + + return talloc_asprintf(mem_ctx, "%"PRIi64":%u:%s.%s:%s:%s:%" PRIu32 "", + sbus_req->sender->uid, sbus_req->type, sbus_req->interface, sbus_req->member, + sbus_req->path, args->arg0, args->arg1); +} + +const char * +_sbus_ifp_key_u_0 + (TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct _sbus_ifp_invoker_args_u *args) +{ + if (sbus_req->sender == NULL) { + return talloc_asprintf(mem_ctx, "-:%u:%s.%s:%s:%" PRIu32 "", + sbus_req->type, sbus_req->interface, sbus_req->member, + sbus_req->path, args->arg0); + } + + return talloc_asprintf(mem_ctx, "%"PRIi64":%u:%s.%s:%s:%" PRIu32 "", + sbus_req->sender->uid, sbus_req->type, sbus_req->interface, sbus_req->member, + sbus_req->path, args->arg0); +} diff --git a/src/responder/ifp/ifp_iface/sbus_ifp_keygens.h b/src/responder/ifp/ifp_iface/sbus_ifp_keygens.h new file mode 100644 index 0000000..a0e5fb2 --- /dev/null +++ b/src/responder/ifp/ifp_iface/sbus_ifp_keygens.h @@ -0,0 +1,57 @@ +/* + Generated by sbus code generator + + Copyright (C) 2017 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/>. +*/ + +#ifndef _SBUS_IFP_KEYGENS_H_ +#define _SBUS_IFP_KEYGENS_H_ + +#include <talloc.h> + +#include "sbus/sbus_request.h" +#include "responder/ifp/ifp_iface/sbus_ifp_arguments.h" + +const char * +_sbus_ifp_key_ + (TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req); + +const char * +_sbus_ifp_key_s_0 + (TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct _sbus_ifp_invoker_args_s *args); + +const char * +_sbus_ifp_key_ssu_0_1_2 + (TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct _sbus_ifp_invoker_args_ssu *args); + +const char * +_sbus_ifp_key_su_0_1 + (TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct _sbus_ifp_invoker_args_su *args); + +const char * +_sbus_ifp_key_u_0 + (TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct _sbus_ifp_invoker_args_u *args); + +#endif /* _SBUS_IFP_KEYGENS_H_ */ diff --git a/src/responder/ifp/ifp_iface/sbus_ifp_server.h b/src/responder/ifp/ifp_iface/sbus_ifp_server.h new file mode 100644 index 0000000..4f65ea6 --- /dev/null +++ b/src/responder/ifp/ifp_iface/sbus_ifp_server.h @@ -0,0 +1,27 @@ +/* + Generated by sbus code generator + + Copyright (C) 2017 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/>. +*/ + +#ifndef _SBUS_IFP_SERVER_H_ +#define _SBUS_IFP_SERVER_H_ + +#include "sbus/sbus.h" +#include "sbus/sbus_interface.h" +#include "responder/ifp/ifp_iface/sbus_ifp_interface.h" + +#endif /* _SBUS_IFP_SERVER_H_ */ diff --git a/src/responder/ifp/ifp_iface/sbus_ifp_symbols.c b/src/responder/ifp/ifp_iface/sbus_ifp_symbols.c new file mode 100644 index 0000000..a861fbc --- /dev/null +++ b/src/responder/ifp/ifp_iface/sbus_ifp_symbols.c @@ -0,0 +1,436 @@ +/* + Generated by sbus code generator + + Copyright (C) 2017 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 "sbus/sbus_interface_declarations.h" +#include "responder/ifp/ifp_iface/sbus_ifp_symbols.h" + +const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_FindBackendByName = { + .input = (const struct sbus_argument[]){ + {.type = "s", .name = "name"}, + {NULL} + }, + .output = (const struct sbus_argument[]){ + {.type = "o", .name = "backend"}, + {NULL} + } +}; + +const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_FindDomainByName = { + .input = (const struct sbus_argument[]){ + {.type = "s", .name = "name"}, + {NULL} + }, + .output = (const struct sbus_argument[]){ + {.type = "o", .name = "domain"}, + {NULL} + } +}; + +const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_FindMonitor = { + .input = (const struct sbus_argument[]){ + {NULL} + }, + .output = (const struct sbus_argument[]){ + {.type = "o", .name = "monitor"}, + {NULL} + } +}; + +const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_FindResponderByName = { + .input = (const struct sbus_argument[]){ + {.type = "s", .name = "name"}, + {NULL} + }, + .output = (const struct sbus_argument[]){ + {.type = "o", .name = "responder"}, + {NULL} + } +}; + +const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_GetUserAttr = { + .input = (const struct sbus_argument[]){ + {.type = "s", .name = "user"}, + {.type = "as", .name = "attr"}, + {NULL} + }, + .output = (const struct sbus_argument[]){ + {.type = "a{sv}", .name = "values"}, + {NULL} + } +}; + +const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_GetUserGroups = { + .input = (const struct sbus_argument[]){ + {.type = "s", .name = "user"}, + {NULL} + }, + .output = (const struct sbus_argument[]){ + {.type = "as", .name = "values"}, + {NULL} + } +}; + +const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_ListBackends = { + .input = (const struct sbus_argument[]){ + {NULL} + }, + .output = (const struct sbus_argument[]){ + {.type = "ao", .name = "backends"}, + {NULL} + } +}; + +const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_ListComponents = { + .input = (const struct sbus_argument[]){ + {NULL} + }, + .output = (const struct sbus_argument[]){ + {.type = "ao", .name = "components"}, + {NULL} + } +}; + +const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_ListDomains = { + .input = (const struct sbus_argument[]){ + {NULL} + }, + .output = (const struct sbus_argument[]){ + {.type = "ao", .name = "domain"}, + {NULL} + } +}; + +const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_ListResponders = { + .input = (const struct sbus_argument[]){ + {NULL} + }, + .output = (const struct sbus_argument[]){ + {.type = "ao", .name = "responders"}, + {NULL} + } +}; + +const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Ping = { + .input = (const struct sbus_argument[]){ + {.type = "s", .name = "ping"}, + {NULL} + }, + .output = (const struct sbus_argument[]){ + {.type = "s", .name = "pong"}, + {NULL} + } +}; + +const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Cache_List = { + .input = (const struct sbus_argument[]){ + {NULL} + }, + .output = (const struct sbus_argument[]){ + {.type = "ao", .name = "result"}, + {NULL} + } +}; + +const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Cache_ListByDomain = { + .input = (const struct sbus_argument[]){ + {.type = "s", .name = "domain_name"}, + {NULL} + }, + .output = (const struct sbus_argument[]){ + {.type = "ao", .name = "result"}, + {NULL} + } +}; + +const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Cache_Object_Remove = { + .input = (const struct sbus_argument[]){ + {NULL} + }, + .output = (const struct sbus_argument[]){ + {.type = "b", .name = "result"}, + {NULL} + } +}; + +const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Cache_Object_Store = { + .input = (const struct sbus_argument[]){ + {NULL} + }, + .output = (const struct sbus_argument[]){ + {.type = "b", .name = "result"}, + {NULL} + } +}; + +const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Domains_Domain_ActiveServer = { + .input = (const struct sbus_argument[]){ + {.type = "s", .name = "service"}, + {NULL} + }, + .output = (const struct sbus_argument[]){ + {.type = "s", .name = "server"}, + {NULL} + } +}; + +const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Domains_Domain_IsOnline = { + .input = (const struct sbus_argument[]){ + {NULL} + }, + .output = (const struct sbus_argument[]){ + {.type = "b", .name = "status"}, + {NULL} + } +}; + +const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Domains_Domain_ListServers = { + .input = (const struct sbus_argument[]){ + {.type = "s", .name = "service_name"}, + {NULL} + }, + .output = (const struct sbus_argument[]){ + {.type = "as", .name = "servers"}, + {NULL} + } +}; + +const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Domains_Domain_ListServices = { + .input = (const struct sbus_argument[]){ + {NULL} + }, + .output = (const struct sbus_argument[]){ + {.type = "as", .name = "services"}, + {NULL} + } +}; + +const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Domains_Domain_RefreshAccessRules = { + .input = (const struct sbus_argument[]){ + {NULL} + }, + .output = (const struct sbus_argument[]){ + {NULL} + } +}; + +const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Groups_FindByID = { + .input = (const struct sbus_argument[]){ + {.type = "u", .name = "id"}, + {NULL} + }, + .output = (const struct sbus_argument[]){ + {.type = "o", .name = "result"}, + {NULL} + } +}; + +const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Groups_FindByName = { + .input = (const struct sbus_argument[]){ + {.type = "s", .name = "name"}, + {NULL} + }, + .output = (const struct sbus_argument[]){ + {.type = "o", .name = "result"}, + {NULL} + } +}; + +const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Groups_ListByDomainAndName = { + .input = (const struct sbus_argument[]){ + {.type = "s", .name = "domain_name"}, + {.type = "s", .name = "name_filter"}, + {.type = "u", .name = "limit"}, + {NULL} + }, + .output = (const struct sbus_argument[]){ + {.type = "ao", .name = "result"}, + {NULL} + } +}; + +const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Groups_ListByName = { + .input = (const struct sbus_argument[]){ + {.type = "s", .name = "name_filter"}, + {.type = "u", .name = "limit"}, + {NULL} + }, + .output = (const struct sbus_argument[]){ + {.type = "ao", .name = "result"}, + {NULL} + } +}; + +const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Groups_Group_UpdateMemberList = { + .input = (const struct sbus_argument[]){ + {NULL} + }, + .output = (const struct sbus_argument[]){ + {NULL} + } +}; + +const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_FindByCertificate = { + .input = (const struct sbus_argument[]){ + {.type = "s", .name = "pem_cert"}, + {NULL} + }, + .output = (const struct sbus_argument[]){ + {.type = "o", .name = "result"}, + {NULL} + } +}; + +const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_FindByID = { + .input = (const struct sbus_argument[]){ + {.type = "u", .name = "id"}, + {NULL} + }, + .output = (const struct sbus_argument[]){ + {.type = "o", .name = "result"}, + {NULL} + } +}; + +const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_FindByName = { + .input = (const struct sbus_argument[]){ + {.type = "s", .name = "name"}, + {NULL} + }, + .output = (const struct sbus_argument[]){ + {.type = "o", .name = "result"}, + {NULL} + } +}; + +const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_FindByNameAndCertificate = { + .input = (const struct sbus_argument[]){ + {.type = "s", .name = "name"}, + {.type = "s", .name = "pem_cert"}, + {NULL} + }, + .output = (const struct sbus_argument[]){ + {.type = "o", .name = "result"}, + {NULL} + } +}; + +const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_FindByValidCertificate = { + .input = (const struct sbus_argument[]){ + {.type = "s", .name = "pem_cert"}, + {NULL} + }, + .output = (const struct sbus_argument[]){ + {.type = "o", .name = "result"}, + {NULL} + } +}; + +const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_ListByAttr = { + .input = (const struct sbus_argument[]){ + {.type = "s", .name = "attribute"}, + {.type = "s", .name = "attr_filter"}, + {.type = "u", .name = "limit"}, + {NULL} + }, + .output = (const struct sbus_argument[]){ + {.type = "ao", .name = "result"}, + {NULL} + } +}; + +const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_ListByCertificate = { + .input = (const struct sbus_argument[]){ + {.type = "s", .name = "pem_cert"}, + {.type = "u", .name = "limit"}, + {NULL} + }, + .output = (const struct sbus_argument[]){ + {.type = "ao", .name = "result"}, + {NULL} + } +}; + +const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_ListByDomainAndName = { + .input = (const struct sbus_argument[]){ + {.type = "s", .name = "domain_name"}, + {.type = "s", .name = "name_filter"}, + {.type = "u", .name = "limit"}, + {NULL} + }, + .output = (const struct sbus_argument[]){ + {.type = "ao", .name = "result"}, + {NULL} + } +}; + +const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_ListByName = { + .input = (const struct sbus_argument[]){ + {.type = "s", .name = "name_filter"}, + {.type = "u", .name = "limit"}, + {NULL} + }, + .output = (const struct sbus_argument[]){ + {.type = "ao", .name = "result"}, + {NULL} + } +}; + +const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_User_UpdateGroupsList = { + .input = (const struct sbus_argument[]){ + {NULL} + }, + .output = (const struct sbus_argument[]){ + {NULL} + } +}; diff --git a/src/responder/ifp/ifp_iface/sbus_ifp_symbols.h b/src/responder/ifp/ifp_iface/sbus_ifp_symbols.h new file mode 100644 index 0000000..f433fa7 --- /dev/null +++ b/src/responder/ifp/ifp_iface/sbus_ifp_symbols.h @@ -0,0 +1,130 @@ +/* + Generated by sbus code generator + + Copyright (C) 2017 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/>. +*/ + +#ifndef _SBUS_IFP_SYMBOLS_H_ +#define _SBUS_IFP_SYMBOLS_H_ + +#include "sbus/sbus_interface_declarations.h" + +extern const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_FindBackendByName; + +extern const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_FindDomainByName; + +extern const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_FindMonitor; + +extern const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_FindResponderByName; + +extern const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_GetUserAttr; + +extern const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_GetUserGroups; + +extern const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_ListBackends; + +extern const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_ListComponents; + +extern const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_ListDomains; + +extern const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_ListResponders; + +extern const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Ping; + +extern const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Cache_List; + +extern const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Cache_ListByDomain; + +extern const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Cache_Object_Remove; + +extern const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Cache_Object_Store; + +extern const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Domains_Domain_ActiveServer; + +extern const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Domains_Domain_IsOnline; + +extern const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Domains_Domain_ListServers; + +extern const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Domains_Domain_ListServices; + +extern const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Domains_Domain_RefreshAccessRules; + +extern const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Groups_FindByID; + +extern const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Groups_FindByName; + +extern const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Groups_ListByDomainAndName; + +extern const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Groups_ListByName; + +extern const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Groups_Group_UpdateMemberList; + +extern const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_FindByCertificate; + +extern const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_FindByID; + +extern const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_FindByName; + +extern const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_FindByNameAndCertificate; + +extern const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_FindByValidCertificate; + +extern const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_ListByAttr; + +extern const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_ListByCertificate; + +extern const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_ListByDomainAndName; + +extern const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_ListByName; + +extern const struct sbus_method_arguments +_sbus_ifp_args_org_freedesktop_sssd_infopipe_Users_User_UpdateGroupsList; + +#endif /* _SBUS_IFP_SYMBOLS_H_ */ diff --git a/src/responder/ifp/ifp_iface_nodes.c b/src/responder/ifp/ifp_iface_nodes.c new file mode 100644 index 0000000..18e5374 --- /dev/null +++ b/src/responder/ifp/ifp_iface_nodes.c @@ -0,0 +1,166 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2015 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 "responder/ifp/ifp_users.h" +#include "responder/ifp/ifp_groups.h" +#include "responder/ifp/ifp_cache.h" +#include "responder/ifp/ifp_domains.h" +#include "responder/ifp/ifp_iface/ifp_iface_async.h" + +static errno_t +nodes_ifp(TALLOC_CTX *mem_ctx, + const char *path, + struct ifp_ctx *ctx, + const char ***_nodes) +{ + static const char *nodes[] = {"Users", "Groups", "Domains", NULL}; + + *_nodes = nodes; + + return EOK; +} + +static errno_t +nodes_cached_objects(TALLOC_CTX *mem_ctx, + struct ifp_ctx *ifp_ctx, + enum ifp_cache_type type, + const char *prefix, + const char ***_nodes) +{ + TALLOC_CTX *tmp_ctx; + const char **paths; + const char **nodes; + const char *node; + int num_paths; + errno_t ret; + int i; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + ret = ifp_cache_list_domains(tmp_ctx, ifp_ctx->rctx->domains, type, &paths); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to obtain cache objects list " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + num_paths = talloc_array_length(paths) - 1; + nodes = talloc_zero_array(tmp_ctx, const char *, num_paths + 1); + if (nodes == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero_array() failed\n"); + ret = ENOMEM; + goto done; + } + + for (i = 0; i < num_paths; i++) { + node = sbus_opath_strip_prefix(paths[i], prefix); + nodes[i] = talloc_strdup(nodes, node); + if (nodes[i] == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup() failed\n"); + ret = ENOMEM; + goto done; + } + } + + *_nodes = talloc_steal(mem_ctx, nodes); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +nodes_users(TALLOC_CTX *mem_ctx, + const char *path, + struct ifp_ctx *ctx, + const char ***_nodes) +{ + return nodes_cached_objects(mem_ctx, ctx, IFP_CACHE_USER, + IFP_PATH_USERS "/", _nodes); +} + +static errno_t +nodes_groups(TALLOC_CTX *mem_ctx, + const char *path, + struct ifp_ctx *ctx, + const char ***_nodes) +{ + return nodes_cached_objects(mem_ctx, ctx, IFP_CACHE_GROUP, + IFP_PATH_GROUPS "/", _nodes); +} + +static errno_t +nodes_domains(TALLOC_CTX *mem_ctx, + const char *path, + struct ifp_ctx *ctx, + const char ***_nodes) +{ + struct sss_domain_info *domain; + const char **nodes; + size_t count; + + count = 0; + domain = ctx->rctx->domains; + do { + count++; + } while ((domain = get_next_domain(domain, SSS_GND_ALL_DOMAINS)) != NULL); + + nodes = talloc_zero_array(mem_ctx, const char *, count + 1); + if (nodes == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero_array() failed\n"); + return ENOMEM; + } + + count = 0; + domain = ctx->rctx->domains; + do { + nodes[count] = sbus_opath_escape(nodes, domain->name); + if (nodes[count] == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sbus_opath_escape_part() failed\n"); + talloc_free(nodes); + return ENOMEM; + } + + count++; + } while ((domain = get_next_domain(domain, SSS_GND_ALL_DOMAINS)) != NULL); + + *_nodes = nodes; + + return EOK; +} + +errno_t +ifp_register_nodes(struct ifp_ctx *ctx, struct sbus_connection *conn) +{ + struct sbus_node nodes[] = SBUS_NODES( + SBUS_NODE_SYNC(IFP_PATH, nodes_ifp, ctx), + SBUS_NODE_SYNC(IFP_PATH_USERS, nodes_users, ctx), + SBUS_NODE_SYNC(IFP_PATH_GROUPS, nodes_groups, ctx), + SBUS_NODE_SYNC(IFP_PATH_DOMAINS, nodes_domains, ctx) + ); + + return sbus_router_add_node_map(conn, nodes); +} diff --git a/src/responder/ifp/ifp_private.h b/src/responder/ifp/ifp_private.h new file mode 100644 index 0000000..e9a9d3a --- /dev/null +++ b/src/responder/ifp/ifp_private.h @@ -0,0 +1,137 @@ +/* + Authors: + Jakub Hrozek <jhrozek@redhat.com> + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2013 Red Hat + + InfoPipe responder: A private header + + 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/>. +*/ + +#ifndef _IFPSRV_PRIVATE_H_ +#define _IFPSRV_PRIVATE_H_ + +#include <talloc.h> +#include <tevent.h> +#include <stdint.h> +#include <ldb.h> + +#include "util/util.h" +#include "confdb/confdb.h" +#include "responder/common/responder.h" +#include "responder/common/negcache.h" +#include "responder/ifp/ifp_iface/ifp_iface_async.h" + +struct ifp_ctx { + struct resp_ctx *rctx; + struct sss_names_ctx *snctx; + + struct sbus_connection *sysbus; + const char **user_whitelist; + uint32_t wildcard_limit; +}; + +errno_t +ifp_access_check(struct sbus_request *sbus_req, + struct ifp_ctx *ifp_ctx); + +errno_t ifp_register_sbus_interface(struct sbus_connection *conn, + struct ifp_ctx *ifp_ctx); + +errno_t +ifp_register_nodes(struct ifp_ctx *ctx, struct sbus_connection *conn); + +errno_t +ifp_ping(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char *ping, + const char **_pong); + +struct tevent_req * +ifp_get_user_attr_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char *name, + const char **attrs, + DBusMessageIter *write_iter); + +errno_t +ifp_get_user_attr_recv(TALLOC_CTX *mem_ctx, struct tevent_req *req); + +struct tevent_req * +ifp_user_get_groups_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char *name); + +errno_t +ifp_user_get_groups_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char ***_groupnames); + +/* == Utility functions == */ + +errno_t ifp_add_value_to_dict(DBusMessageIter *iter_dict, + const char *key, + const char *value); + +errno_t ifp_add_ldb_el_to_dict(DBusMessageIter *iter_dict, + struct ldb_message_element *el); +const char ** +ifp_parse_user_attr_list(TALLOC_CTX *mem_ctx, const char *conf_str); + +const char ** +ifp_get_user_extra_attributes(TALLOC_CTX *mem_ctx, struct ifp_ctx *ifp_ctx); + +bool ifp_attr_allowed(const char *whitelist[], const char *attr); +bool ifp_is_user_attr_allowed(struct ifp_ctx *ifp_ctx, const char *attr); + +/* Used for list calls */ +struct ifp_list_ctx { + const char *attr; + const char *filter; + uint32_t limit; + + struct sss_domain_info *dom; + struct ifp_ctx *ctx; + + const char **paths; + size_t paths_max; + size_t path_count; +}; + +struct ifp_list_ctx *ifp_list_ctx_new(TALLOC_CTX *mem_ctx, + struct ifp_ctx *ctx, + const char *attr, + const char *filter, + uint32_t limit); + +errno_t ifp_list_ctx_remaining_capacity(struct ifp_list_ctx *list_ctx, + size_t entries, + size_t *_capacity); + +errno_t ifp_ldb_el_output_name(struct resp_ctx *rctx, + struct ldb_message *msg, + const char *el_name, + struct sss_domain_info *dom); + +char *ifp_format_name_attr(TALLOC_CTX *mem_ctx, struct ifp_ctx *ifp_ctx, + const char *in_name, struct sss_domain_info *dom); + +#endif /* _IFPSRV_PRIVATE_H_ */ diff --git a/src/responder/ifp/ifp_users.c b/src/responder/ifp/ifp_users.c new file mode 100644 index 0000000..7acd46e --- /dev/null +++ b/src/responder/ifp/ifp_users.c @@ -0,0 +1,2040 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2015 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 <talloc.h> +#include <tevent.h> +#include <string.h> + +#include "db/sysdb.h" +#include "util/util.h" +#include "util/strtonum.h" +#include "util/cert.h" +#include "util/child_common.h" +#include "util/crypto/sss_crypto.h" +#include "responder/common/responder.h" +#include "responder/common/cache_req/cache_req.h" +#include "responder/ifp/ifp_users.h" +#include "responder/ifp/ifp_groups.h" +#include "responder/ifp/ifp_cache.h" +#include "responder/ifp/ifp_iface/ifp_iface_async.h" + +char * ifp_users_build_path_from_msg(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + struct ldb_message *msg) +{ + const char *key = NULL; + + switch (domain->type) { + case DOM_TYPE_APPLICATION: + key = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); + break; + case DOM_TYPE_POSIX: + key = ldb_msg_find_attr_as_string(msg, SYSDB_UIDNUM, NULL); + break; + } + + + if (key == NULL) { + return NULL; + } + + return sbus_opath_compose(mem_ctx, IFP_PATH_USERS, domain->name, key); +} + +static errno_t ifp_users_decompose_path(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domains, + const char *path, + struct sss_domain_info **_domain, + char **_key) +{ + char **parts = NULL; + struct sss_domain_info *domain; + errno_t ret; + + ret = sbus_opath_decompose_expected(NULL, path, IFP_PATH_USERS, 2, &parts); + if (ret != EOK) { + return ret; + } + + domain = find_domain_by_name(domains, parts[0], false); + if (domain == NULL) { + ret = ERR_DOMAIN_NOT_FOUND; + goto done; + } + + *_domain = domain; + *_key = talloc_steal(mem_ctx, parts[1]); + +done: + talloc_free(parts); + return ret; +} + +static int ifp_users_list_copy(struct ifp_list_ctx *list_ctx, + struct sss_domain_info *domain, + struct ldb_result *result) +{ + size_t copy_count, i; + errno_t ret; + + ret = ifp_list_ctx_remaining_capacity(list_ctx, result->count, ©_count); + if (ret != EOK) { + goto done; + } + + for (i = 0; i < copy_count; i++) { + list_ctx->paths[list_ctx->path_count + i] = \ + ifp_users_build_path_from_msg(list_ctx->paths, + domain, + result->msgs[i]); + if (list_ctx->paths[list_ctx->path_count + i] == NULL) { + ret = ENOMEM; + goto done; + } + } + + list_ctx->path_count += copy_count; + ret = EOK; + +done: + return ret; +} + +struct ifp_users_find_by_name_state { + const char *path; +}; + +static void ifp_users_find_by_name_done(struct tevent_req *subreq); + +struct tevent_req * +ifp_users_find_by_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char *name) +{ + struct ifp_users_find_by_name_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct ifp_users_find_by_name_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + subreq = cache_req_user_by_name_send(state, ctx->rctx->ev, ctx->rctx, + ctx->rctx->ncache, 0, + CACHE_REQ_ANY_DOM, NULL, + name); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ifp_users_find_by_name_done, req); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void ifp_users_find_by_name_done(struct tevent_req *subreq) +{ + struct ifp_users_find_by_name_state *state; + struct cache_req_result *result; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ifp_users_find_by_name_state); + + ret = cache_req_user_by_name_recv(state, subreq, &result); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to find user [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + state->path = ifp_users_build_path_from_msg(state, result->domain, + result->msgs[0]); + if (state->path == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_done(req); + return; +} + +errno_t +ifp_users_find_by_name_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char **_path) +{ + struct ifp_users_find_by_name_state *state; + state = tevent_req_data(req, struct ifp_users_find_by_name_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_path = talloc_steal(mem_ctx, state->path); + + return EOK; +} + +struct ifp_users_find_by_id_state { + const char *path; +}; + +static void ifp_users_find_by_id_done(struct tevent_req *subreq); + +struct tevent_req * +ifp_users_find_by_id_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + uint32_t id) +{ + struct ifp_users_find_by_id_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ifp_users_find_by_id_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + subreq = cache_req_user_by_id_send(state, ctx->rctx->ev, ctx->rctx, + ctx->rctx->ncache, 0, NULL, id); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ifp_users_find_by_id_done, req); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void ifp_users_find_by_id_done(struct tevent_req *subreq) +{ + struct ifp_users_find_by_id_state *state; + struct cache_req_result *result; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ifp_users_find_by_id_state); + + ret = cache_req_user_by_id_recv(state, subreq, &result); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to find user [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + state->path = ifp_users_build_path_from_msg(state, result->domain, + result->msgs[0]); + if (state->path == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_done(req); + return; +} + +errno_t +ifp_users_find_by_id_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char **_path) +{ + struct ifp_users_find_by_id_state *state; + state = tevent_req_data(req, struct ifp_users_find_by_id_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_path = talloc_steal(mem_ctx, state->path); + + return EOK; +} + +struct ifp_users_find_by_cert_state { + const char *path; +}; + +static void ifp_users_find_by_cert_done(struct tevent_req *subreq); + +struct tevent_req * +ifp_users_find_by_cert_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char *pem_cert) +{ + struct ifp_users_find_by_cert_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + char *derb64; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ifp_users_find_by_cert_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + ret = sss_cert_pem_to_derb64(state, pem_cert, &derb64); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_cert_pem_to_derb64 failed.\n"); + goto done; + } + + subreq = cache_req_user_by_cert_send(state, ctx->rctx->ev, ctx->rctx, + ctx->rctx->ncache, 0, CACHE_REQ_ANY_DOM, + NULL, derb64); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ifp_users_find_by_cert_done, req); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void ifp_users_find_by_cert_done(struct tevent_req *subreq) +{ + struct ifp_users_find_by_cert_state *state; + struct cache_req_result *result; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ifp_users_find_by_cert_state); + + ret = cache_req_user_by_cert_recv(state, subreq, &result); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to find user [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (result->count > 1) { + DEBUG(SSSDBG_CRIT_FAILURE, "More than one user found. " + "Use ListByCertificate to get all.\n"); + tevent_req_error(req, EINVAL); + return; + } + + state->path = ifp_users_build_path_from_msg(state, result->domain, + result->msgs[0]); + if (state->path == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_done(req); + return; +} + +errno_t +ifp_users_find_by_cert_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char **_path) +{ + struct ifp_users_find_by_cert_state *state; + state = tevent_req_data(req, struct ifp_users_find_by_cert_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_path = talloc_steal(mem_ctx, state->path); + + return EOK; +} + +struct ifp_users_list_by_cert_state { + struct ifp_ctx *ifp_ctx; + struct ifp_list_ctx *list_ctx; + char *derb64; +}; + +static errno_t ifp_users_list_by_cert_step(struct tevent_req *req); +static void ifp_users_list_by_cert_done(struct tevent_req *subreq); + +struct tevent_req * +ifp_users_list_by_cert_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char *pem_cert, + uint32_t limit) +{ + struct ifp_users_list_by_cert_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ifp_users_list_by_cert_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + ret = sss_cert_pem_to_derb64(state, pem_cert, &state->derb64); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_cert_pem_to_derb64 failed.\n"); + goto done; + } + + state->ifp_ctx = ctx; + state->list_ctx = ifp_list_ctx_new(state, ctx, NULL, state->derb64, limit); + if (state->list_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ret = ifp_users_list_by_cert_step(req); + +done: + if (ret == EOK) { + tevent_req_done(req); + tevent_req_post(req, ev); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static errno_t +ifp_users_list_by_cert_step(struct tevent_req *req) +{ + struct ifp_users_list_by_cert_state *state; + struct tevent_req *subreq; + + state = tevent_req_data(req, struct ifp_users_list_by_cert_state); + + if (state->list_ctx->dom == NULL) { + return EOK; + } + + subreq = cache_req_user_by_cert_send(state->list_ctx, + state->ifp_ctx->rctx->ev, + state->ifp_ctx->rctx, + state->ifp_ctx->rctx->ncache, + 0, + CACHE_REQ_ANY_DOM, + state->list_ctx->dom->name, + state->list_ctx->filter); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + return ENOMEM; + } + + tevent_req_set_callback(subreq, ifp_users_list_by_cert_done, req); + + state->list_ctx->dom = get_next_domain(state->list_ctx->dom, + SSS_GND_DESCEND); + + return EAGAIN; +} + +static void ifp_users_list_by_cert_done(struct tevent_req *subreq) +{ + struct ifp_users_list_by_cert_state *state; + struct cache_req_result *result; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ifp_users_list_by_cert_state); + + ret = cache_req_user_by_cert_recv(state, subreq, &result); + talloc_zfree(subreq); + if (ret == EOK) { + ret = ifp_users_list_copy(state->list_ctx, result->domain, + result->ldb_result); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to copy domain result\n"); + tevent_req_error(req, ret); + return; + } + } else if (ret != ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to list groups [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + ret = ifp_users_list_by_cert_step(req); + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } +} + +errno_t +ifp_users_list_by_cert_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char ***_paths) +{ + struct ifp_users_list_by_cert_state *state; + state = tevent_req_data(req, struct ifp_users_list_by_cert_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_paths = talloc_steal(mem_ctx, state->list_ctx->paths); + + return EOK; +} + +struct ifp_users_find_by_name_and_cert_state { + struct ifp_ctx *ifp_ctx; + struct ifp_list_ctx *list_ctx; + const char *name; + const char *pem_cert; + char *derb64; + + const char *user_opath; +}; + +static void ifp_users_find_by_name_and_cert_name_done(struct tevent_req *subreq); +static errno_t ifp_users_find_by_name_and_cert_step(struct tevent_req *req); +static void ifp_users_find_by_name_and_cert_done(struct tevent_req *subreq); + +struct tevent_req * +ifp_users_find_by_name_and_cert_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char *name, + const char *pem_cert) +{ + struct ifp_users_find_by_name_and_cert_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ifp_users_find_by_name_and_cert_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + state->ifp_ctx = ctx; + + if (!SBUS_REQ_STRING_IS_EMPTY(name)) { + state->name = talloc_strdup(state, name); + if (state->name == NULL) { + ret = ENOMEM; + goto done; + } + } + + if (!SBUS_REQ_STRING_IS_EMPTY(pem_cert)) { + state->pem_cert = talloc_strdup(state, pem_cert); + if (state->pem_cert == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sss_cert_pem_to_derb64(state, pem_cert, &state->derb64); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_cert_pem_to_derb64 failed.\n"); + goto done; + } + + /* FIXME: if unlimted searches with limit=0 will work please replace + * 100 with 0. */ + state->list_ctx = ifp_list_ctx_new(state, ctx, NULL, state->derb64, 100); + if (state->list_ctx == NULL) { + ret = ENOMEM; + goto done; + } + } + + if (state->name == NULL && state->pem_cert == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Empty arguments!\n"); + ret = EINVAL; + goto done; + } + + if (state->name != NULL) { + subreq = cache_req_user_by_name_send(state, ctx->rctx->ev, ctx->rctx, + ctx->rctx->ncache, 0, + CACHE_REQ_ANY_DOM, + NULL, state->name); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ifp_users_find_by_name_and_cert_name_done, req); + } else { + ret = ifp_users_find_by_name_and_cert_step(req); + goto done; + } + + ret = EAGAIN; + +done: + if (ret == EOK) { + tevent_req_done(req); + tevent_req_post(req, ev); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void ifp_users_find_by_name_and_cert_name_done(struct tevent_req *subreq) +{ + struct ifp_users_find_by_name_and_cert_state *state; + struct cache_req_result *result; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ifp_users_find_by_name_and_cert_state); + + ret = cache_req_user_by_name_recv(state, subreq, &result); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + state->user_opath = ifp_users_build_path_from_msg(state, + result->domain, + result->msgs[0]); + if (state->user_opath == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + ret = ifp_users_find_by_name_and_cert_step(req); + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } + + return; +} + +static errno_t +ifp_users_find_by_name_and_cert_step(struct tevent_req *req) +{ + struct ifp_users_find_by_name_and_cert_state *state; + struct tevent_req *subreq; + + state = tevent_req_data(req, struct ifp_users_find_by_name_and_cert_state); + + if (state->list_ctx == NULL) { + if (state->name == NULL) { + return EINVAL; + } + + /* Nothing to search for. */ + return EOK; + } + + /* No more domains to try. */ + if (state->list_ctx->dom == NULL) { + return EOK; + } + + subreq = cache_req_user_by_cert_send(state->list_ctx, + state->ifp_ctx->rctx->ev, + state->ifp_ctx->rctx, + state->ifp_ctx->rctx->ncache, + 0, + CACHE_REQ_ANY_DOM, + state->list_ctx->dom->name, + state->list_ctx->filter); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + return ENOMEM; + } + + tevent_req_set_callback(subreq, ifp_users_find_by_name_and_cert_done, req); + + state->list_ctx->dom = get_next_domain(state->list_ctx->dom, + SSS_GND_DESCEND); + + return EAGAIN; +} + +static void ifp_users_find_by_name_and_cert_done(struct tevent_req *subreq) +{ + struct ifp_users_find_by_name_and_cert_state *state; + struct cache_req_result *result; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ifp_users_find_by_name_and_cert_state); + + ret = cache_req_user_by_cert_recv(state, subreq, &result); + talloc_zfree(subreq); + if (ret == EOK) { + ret = ifp_users_list_copy(state->list_ctx, result->domain, + result->ldb_result); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to copy domain result\n"); + tevent_req_error(req, ret); + return; + } + } else if (ret != ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to list groups [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + ret = ifp_users_find_by_name_and_cert_step(req); + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } + + return; +} + +errno_t +ifp_users_find_by_name_and_cert_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char **_path) +{ + struct ifp_users_find_by_name_and_cert_state *state; + size_t c; + + state = tevent_req_data(req, struct ifp_users_find_by_name_and_cert_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + /* If no name was given check if there is only one user mapped to the + * certificate and return its object path. Either no or more than one + * mapped users are errors in this case. + * The case where a given name could not be found is already handled in + * ifp_users_find_by_name_and_cert_name_done(). */ + if (state->user_opath == NULL) { + if (state->list_ctx == NULL || state->list_ctx->path_count == 0) { + return ENOENT; + } else if (state->list_ctx->path_count == 1) { + *_path = talloc_steal(mem_ctx, state->list_ctx->paths[0]); + return EOK; + } else { + return EEXIST; + } + } + + /* If there was no certificate given just return the object path of the + * user found by name. If a certificate was given an no mapped user was + * found return an error. */ + if (state->pem_cert == NULL) { + *_path = talloc_steal(mem_ctx, state->user_opath); + return EOK; + } else { + for (c = 0; c < state->list_ctx->path_count; c++) { + if (strcmp(state->user_opath, state->list_ctx->paths[c]) == 0) { + *_path = talloc_steal(mem_ctx, state->user_opath); + return EOK; + } + } + } + + return ENOENT; +} + +struct ifp_users_list_by_attr_state { + struct ifp_ctx *ifp_ctx; + struct ifp_list_ctx *list_ctx; +}; + +static errno_t ifp_users_list_by_attr_step(struct tevent_req *req); +static void ifp_users_list_by_attr_done(struct tevent_req *subreq); + +struct tevent_req * +ifp_users_list_by_attr_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char *attr, + const char *filter, + uint32_t limit) +{ + struct ifp_users_list_by_attr_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ifp_users_list_by_attr_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + state->ifp_ctx = ctx; + state->list_ctx = ifp_list_ctx_new(state, ctx, attr, filter, limit); + if (state->list_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ret = ifp_users_list_by_attr_step(req); + +done: + if (ret == EOK) { + tevent_req_done(req); + tevent_req_post(req, ev); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static errno_t +ifp_users_list_by_attr_step(struct tevent_req *req) +{ + struct ifp_users_list_by_attr_state *state; + struct tevent_req *subreq; + + state = tevent_req_data(req, struct ifp_users_list_by_attr_state); + + if (state->list_ctx->dom == NULL) { + return EOK; + } + + subreq = cache_req_user_by_filter_send(state->list_ctx, + state->ifp_ctx->rctx->ev, + state->ifp_ctx->rctx, + CACHE_REQ_ANY_DOM, + state->list_ctx->dom->name, + state->list_ctx->attr, + state->list_ctx->filter); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + return ENOMEM; + } + + tevent_req_set_callback(subreq, ifp_users_list_by_attr_done, req); + + state->list_ctx->dom = get_next_domain(state->list_ctx->dom, + SSS_GND_DESCEND); + + return EAGAIN; +} + +static void ifp_users_list_by_attr_done(struct tevent_req *subreq) +{ + struct ifp_users_list_by_attr_state *state; + struct cache_req_result *result; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ifp_users_list_by_attr_state); + + ret = cache_req_user_by_name_recv(state, subreq, &result); + talloc_zfree(subreq); + if (ret == EOK) { + ret = ifp_users_list_copy(state->list_ctx, result->domain, + result->ldb_result); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to copy domain result\n"); + tevent_req_error(req, ret); + return; + } + } else if (ret != ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to list users [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + ret = ifp_users_list_by_attr_step(req); + if (ret == EOK) { + tevent_req_done(req); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + } +} + +errno_t +ifp_users_list_by_attr_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char ***_paths) +{ + struct ifp_users_list_by_attr_state *state; + state = tevent_req_data(req, struct ifp_users_list_by_attr_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_paths = talloc_steal(mem_ctx, state->list_ctx->paths); + + return EOK; +} + +struct ifp_users_list_by_domain_and_name_state { + struct ifp_list_ctx *list_ctx; +}; + +static void ifp_users_list_by_domain_and_name_done(struct tevent_req *subreq); + +struct tevent_req * +ifp_users_list_by_domain_and_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char *domain, + const char *filter, + uint32_t limit) +{ + struct ifp_users_list_by_domain_and_name_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ifp_users_list_by_domain_and_name_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + state->list_ctx = ifp_list_ctx_new(state, ctx, NULL, filter, limit); + if (state->list_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + subreq = cache_req_user_by_filter_send(state->list_ctx, ctx->rctx->ev, + ctx->rctx, CACHE_REQ_ANY_DOM, + domain, NULL, filter); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ifp_users_list_by_domain_and_name_done, req); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void ifp_users_list_by_domain_and_name_done(struct tevent_req *subreq) +{ + struct ifp_users_list_by_domain_and_name_state *state; + struct cache_req_result *result; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ifp_users_list_by_domain_and_name_state); + + ret = cache_req_user_by_name_recv(state, subreq, &result); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + ret = ifp_users_list_copy(state->list_ctx, result->domain, + result->ldb_result); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to copy domain result\n"); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +errno_t +ifp_users_list_by_domain_and_name_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char ***_paths) +{ + struct ifp_users_list_by_domain_and_name_state *state; + state = tevent_req_data(req, struct ifp_users_list_by_domain_and_name_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_paths = talloc_steal(mem_ctx, state->list_ctx->paths); + + return EOK; +} + +struct ifp_users_find_by_valid_cert_state { + struct ifp_ctx *ifp_ctx; + struct tevent_context *ev; + const char *logfile; + int timeout; + char *ca_db; + char *verify_opts; + char *derb64; + const char **extra_args; + const char *path; + + struct sss_child_ctx_old *child_ctx; + struct child_io_fds *io; +}; + +static errno_t p11_child_exec(struct tevent_req *req); +static void +ifp_users_find_by_valid_cert_step(int child_status, + struct tevent_signal *sige, + void *pvt); +static void ifp_users_find_by_valid_cert_done(struct tevent_req *subreq); + +struct tevent_req * +ifp_users_find_by_valid_cert_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char *pem_cert) +{ + struct tevent_req *req; + struct ifp_users_find_by_valid_cert_state *state; + size_t arg_c = 0; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct ifp_users_find_by_valid_cert_state); + if (req == NULL) { + return NULL; + } + + state->ifp_ctx = ctx; + + ret = confdb_get_string(ctx->rctx->cdb, state, + CONFDB_IFP_CONF_ENTRY, CONFDB_SSH_CA_DB, + CONFDB_DEFAULT_SSH_CA_DB, &state->ca_db); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Error reading CA DB from confdb (%d) [%s]\n", + ret, strerror(ret)); + goto done; + } + + ret = confdb_get_int(ctx->rctx->cdb, CONFDB_IFP_CONF_ENTRY, + CONFDB_PAM_P11_CHILD_TIMEOUT, -1, + &state->timeout); + if (ret != EOK || state->timeout == -1) { + /* check pam configuration as well or use default */ + ret = confdb_get_int(ctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_P11_CHILD_TIMEOUT, + P11_CHILD_TIMEOUT_DEFAULT, + &state->timeout); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read p11_child_timeout from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + } + + ret = confdb_get_string(ctx->rctx->cdb, state, CONFDB_MONITOR_CONF_ENTRY, + CONFDB_MONITOR_CERT_VERIFICATION, NULL, + &state->verify_opts); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read '"CONFDB_MONITOR_CERT_VERIFICATION"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + state->ev = ev; + state->logfile = P11_CHILD_LOG_FILE; + state->io = talloc(state, struct child_io_fds); + if (state->io == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc failed.\n"); + ret = ENOMEM; + goto done; + } + state->io->write_to_child_fd = -1; + state->io->read_from_child_fd = -1; + talloc_set_destructor((void *) state->io, child_io_destructor); + + ret = sss_cert_pem_to_derb64(state, pem_cert, &state->derb64); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_cert_pem_to_derb64 failed.\n"); + goto done; + } + + state->extra_args = talloc_zero_array(state, const char *, 8); + if (state->extra_args == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + ret = ENOMEM; + goto done; + } + state->extra_args[arg_c++] = state->derb64; + state->extra_args[arg_c++] = "--certificate"; + state->extra_args[arg_c++] = state->ca_db; + state->extra_args[arg_c++] = "--ca_db"; + if (state->verify_opts != NULL) { + state->extra_args[arg_c++] = state->verify_opts; + state->extra_args[arg_c++] = "--verify"; + } + state->extra_args[arg_c++] = "--verification"; + + ret = p11_child_exec(req); + +done: + if (ret == EOK) { + tevent_req_done(req); + tevent_req_post(req, ev); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static errno_t p11_child_exec(struct tevent_req *req) +{ + struct ifp_users_find_by_valid_cert_state *state; + int pipefd_from_child[2] = PIPE_INIT; + int pipefd_to_child[2] = PIPE_INIT; + pid_t child_pid; + struct timeval tv; + bool endtime; + int ret; + + state = tevent_req_data(req, struct ifp_users_find_by_valid_cert_state); + + ret = pipe(pipefd_from_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + ret = pipe(pipefd_to_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + + child_pid = fork(); + if (child_pid == 0) { /* child */ + exec_child_ex(state, pipefd_to_child, pipefd_from_child, + P11_CHILD_PATH, state->logfile, state->extra_args, + false, STDIN_FILENO, STDOUT_FILENO); + /* We should never get here */ + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "BUG: Could not exec p11 child\n"); + return ret; + } else if (child_pid > 0) { /* parent */ + state->io->read_from_child_fd = pipefd_from_child[0]; + PIPE_FD_CLOSE(pipefd_from_child[1]); + sss_fd_nonblocking(state->io->read_from_child_fd); + + state->io->write_to_child_fd = pipefd_to_child[1]; + PIPE_FD_CLOSE(pipefd_to_child[0]); + sss_fd_nonblocking(state->io->write_to_child_fd); + + /* Set up SIGCHLD handler */ + ret = child_handler_setup(state->ev, child_pid, + ifp_users_find_by_valid_cert_step, + req, &state->child_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not set up child handlers [%d]: %s\n", + ret, sss_strerror(ret)); + ret = ERR_P11_CHILD; + goto done; + } + + /* Set up timeout handler */ + tv = tevent_timeval_current_ofs(state->timeout, 0); + endtime = tevent_req_set_endtime(req, state->ev, tv); + if (endtime == false) { + ret = ERR_P11_CHILD; + goto done; + } + /* Now either wait for the timeout to fire or the child to finish */ + } else { /* error */ + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "fork failed [%d][%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + return EAGAIN; + +done: + if (ret != EOK) { + PIPE_CLOSE(pipefd_from_child); + PIPE_CLOSE(pipefd_to_child); + } + + return ret; +} + +static void +ifp_users_find_by_valid_cert_step(int child_status, + struct tevent_signal *sige, + void *pvt) +{ + struct tevent_req *subreq; + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + struct ifp_users_find_by_valid_cert_state *state; + errno_t ret; + + state = tevent_req_data(req, struct ifp_users_find_by_valid_cert_state); + + PIPE_FD_CLOSE(state->io->read_from_child_fd); + PIPE_FD_CLOSE(state->io->write_to_child_fd); + + if (WIFEXITED(child_status)) { + if (WEXITSTATUS(child_status) == CA_DB_NOT_FOUND_EXIT_CODE) { + DEBUG(SSSDBG_OP_FAILURE, + P11_CHILD_PATH " failed [%d]: [%s].\n", + ERR_CA_DB_NOT_FOUND, sss_strerror(ERR_CA_DB_NOT_FOUND)); + tevent_req_error(req, ERR_CA_DB_NOT_FOUND); + return; + } else if (WEXITSTATUS(child_status) != 0) { + DEBUG(SSSDBG_OP_FAILURE, + P11_CHILD_PATH " failed with status [%d]. Check p11_child" + " logs for more information.\n", + WEXITSTATUS(child_status)); + tevent_req_error(req, ERR_INVALID_CERT); + return; + } + } else if (WIFSIGNALED(child_status)) { + DEBUG(SSSDBG_OP_FAILURE, + P11_CHILD_PATH " was terminated by signal [%d]. Check p11_child" + " logs for more information.\n", + WTERMSIG(child_status)); + tevent_req_error(req, ECHILD); + return; + } + + DEBUG(SSSDBG_TRACE_LIBS, "Certificate [%s] is valid.\n", + state->extra_args[0]); + + subreq = cache_req_user_by_cert_send(state, state->ifp_ctx->rctx->ev, + state->ifp_ctx->rctx, + state->ifp_ctx->rctx->ncache, 0, + CACHE_REQ_ANY_DOM, NULL, + state->derb64); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ifp_users_find_by_valid_cert_done, req); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, state->ifp_ctx->rctx->ev); + } + + return; +} + +static void ifp_users_find_by_valid_cert_done(struct tevent_req *subreq) +{ + struct ifp_users_find_by_valid_cert_state *state; + struct cache_req_result *result; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ifp_users_find_by_valid_cert_state); + + ret = cache_req_user_by_cert_recv(state, subreq, &result); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to find user [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (result->count > 1) { + DEBUG(SSSDBG_CRIT_FAILURE, "More than one user found. " + "Use ListByCertificate to get all.\n"); + tevent_req_error(req, EINVAL); + return; + } + + state->path = ifp_users_build_path_from_msg(state, result->domain, + result->msgs[0]); + if (state->path == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_done(req); + return; +} + +errno_t +ifp_users_find_by_valid_cert_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char **_path) +{ + struct ifp_users_find_by_valid_cert_state *state; + + state = tevent_req_data(req, struct ifp_users_find_by_valid_cert_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_path = talloc_steal(mem_ctx, state->path); + + return EOK; +} + +static errno_t +ifp_users_get_from_cache(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *key, + struct ldb_message **_user) +{ + struct ldb_result *user_res = NULL; + errno_t ret; + uid_t uid; + char *endptr; + + switch (domain->type) { + case DOM_TYPE_POSIX: + uid = strtouint32(key, &endptr, 10); + if ((errno != 0) || *endptr || (key == endptr)) { + ret = errno ? errno : EINVAL; + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid UID value\n"); + goto done; + } + + ret = sysdb_getpwuid_with_views(mem_ctx, domain, uid, &user_res); + if (ret == EOK && user_res->count == 0) { + *_user = NULL; + ret = ENOENT; + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to lookup user %u@%s [%d]: %s\n", + uid, domain->name, ret, sss_strerror(ret)); + goto done; + } + break; + case DOM_TYPE_APPLICATION: + ret = sysdb_getpwnam_with_views(mem_ctx, domain, key, &user_res); + if (ret == EOK && user_res->count == 0) { + *_user = NULL; + ret = ENOENT; + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to lookup user %s@%s [%d]: %s\n", + key, domain->name, ret, sss_strerror(ret)); + goto done; + } + break; + } + + if (user_res->count > 1) { + DEBUG(SSSDBG_CRIT_FAILURE, "More users matched by the single key\n"); + ret = EIO; + goto done; + } + + *_user = talloc_steal(mem_ctx, user_res->msgs[0]); + + ret = EOK; + +done: + talloc_free(user_res); + + return ret; +} + +static errno_t +ifp_users_user_get(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ifp_ctx, + struct sss_domain_info **_domain, + struct ldb_message **_user) +{ + struct sss_domain_info *domain; + char *key; + errno_t ret; + + ret = ifp_users_decompose_path(NULL, + ifp_ctx->rctx->domains, sbus_req->path, + &domain, &key); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to decompose object path" + "[%s] [%d]: %s\n", sbus_req->path, ret, sss_strerror(ret)); + return ret; + } + + if (_user != NULL) { + ret = ifp_users_get_from_cache(mem_ctx, domain, key, _user); + } + + talloc_free(key); + + if (ret == EOK || ret == ENOENT) { + if (_domain != NULL) { + *_domain = domain; + } + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to retrieve user from cache\n"); + } + + return ret; +} + +static errno_t +ifp_users_get_as_string(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ifp_ctx, + const char *attr, + const char **_out, + struct sss_domain_info **_domain) +{ + struct ldb_message *msg; + struct sss_domain_info *domain; + const char *out; + errno_t ret; + + ret = ifp_users_user_get(NULL, sbus_req, ifp_ctx, &domain, &msg); + if (ret != EOK) { + return ret; + } + + out = sss_view_ldb_msg_find_attr_as_string(domain, msg, attr, NULL); + if (out == NULL) { + talloc_free(msg); + return ENOENT; + } + + *_out = talloc_steal(mem_ctx, out); + talloc_free(msg); + + if (_domain != NULL) { + *_domain = domain; + } + + return EOK; +} + +static errno_t +ifp_users_get_name(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ifp_ctx, + const char *attr, + const char **_out) +{ + struct sss_domain_info *domain; + const char *in_name; + const char *out; + errno_t ret; + + ret = ifp_users_get_as_string(NULL, sbus_req, ifp_ctx, attr, + &in_name, &domain); + if (ret != EOK) { + return ret; + } + + out = ifp_format_name_attr(mem_ctx, ifp_ctx, in_name, domain); + talloc_free(discard_const(in_name)); + if (out == NULL) { + return ENOMEM; + } + + *_out = out; + + return EOK; +} + +static errno_t +ifp_users_get_as_uint32(struct sbus_request *sbus_req, + struct ifp_ctx *ifp_ctx, + const char *attr, + uint32_t *_out) +{ + struct ldb_message *msg; + struct sss_domain_info *domain; + errno_t ret; + + ret = ifp_users_user_get(NULL, sbus_req, ifp_ctx, &domain, &msg); + if (ret != EOK) { + return ret; + } + + *_out = sss_view_ldb_msg_find_attr_as_uint64(domain, msg, attr, 0); + talloc_free(msg); + + return EOK; +} + +struct ifp_users_user_update_groups_list_state { + int dummy; +}; + +static void ifp_users_user_update_groups_list_done(struct tevent_req *subreq); + +struct tevent_req * +ifp_users_user_update_groups_list_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx) +{ + struct ifp_users_user_update_groups_list_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + struct sss_domain_info *domain; + struct ldb_message *user; + const char *username; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ifp_users_user_update_groups_list_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + ret = ifp_users_user_get(state, sbus_req, ctx, &domain, &user); + if (ret != EOK) { + goto done; + } + + username = ldb_msg_find_attr_as_string(user, SYSDB_NAME, NULL); + if (username == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "User name is empty!\n"); + ret = ERR_INTERNAL; + goto done; + } + + subreq = cache_req_initgr_by_name_send(state, ctx->rctx->ev, ctx->rctx, + ctx->rctx->ncache, 0, + CACHE_REQ_ANY_DOM, domain->name, + username); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ifp_users_user_update_groups_list_done, req); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void ifp_users_user_update_groups_list_done(struct tevent_req *subreq) +{ + struct ifp_users_user_update_groups_list_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ifp_users_user_update_groups_list_state); + + ret = cache_req_initgr_by_name_recv(state, subreq, NULL); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +errno_t +ifp_users_user_update_groups_list_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +errno_t +ifp_users_user_get_name(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char **_out) +{ + return ifp_users_get_name(mem_ctx, sbus_req, ctx, SYSDB_NAME, _out); +} + +errno_t +ifp_users_user_get_uid_number(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + uint32_t *_out) +{ + return ifp_users_get_as_uint32(sbus_req, ctx, SYSDB_UIDNUM, _out); +} + +errno_t +ifp_users_user_get_gid_number(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + uint32_t *_out) +{ + return ifp_users_get_as_uint32(sbus_req, ctx, SYSDB_GIDNUM, _out); +} + +errno_t +ifp_users_user_get_gecos(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char **_out) +{ + return ifp_users_get_as_string(mem_ctx, sbus_req, ctx, SYSDB_GECOS, _out, NULL); +} + +errno_t +ifp_users_user_get_home_directory(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char **_out) +{ + return ifp_users_get_as_string(mem_ctx, sbus_req, ctx, SYSDB_HOMEDIR, _out, NULL); +} + +errno_t +ifp_users_user_get_login_shell(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char **_out) +{ + return ifp_users_get_as_string(mem_ctx, sbus_req, ctx, SYSDB_SHELL, _out, NULL); +} + +errno_t +ifp_users_user_get_unique_id(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char **_out) +{ + return ifp_users_get_as_string(mem_ctx, sbus_req, ctx, SYSDB_UUID, _out, NULL); +} + +errno_t +ifp_users_user_get_groups(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ifp_ctx, + const char ***_out) +{ + TALLOC_CTX *tmp_ctx; + struct sss_domain_info *domain; + const char *username; + struct ldb_message *user; + struct ldb_result *res; + const char **out; + int num_groups; + gid_t gid; + errno_t ret; + int i; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + ret = ifp_users_user_get(tmp_ctx, sbus_req, ifp_ctx, &domain, &user); + if (ret != EOK) { + return ret; + } + + username = ldb_msg_find_attr_as_string(user, SYSDB_NAME, NULL); + if (username == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "User name is empty!\n"); + return ERR_INTERNAL; + } + + /* Run initgroups. */ + ret = sysdb_initgroups_with_views(tmp_ctx, domain, username, &res); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get groups for %s@%s [%d]: %s\n", + username, domain->name, ret, sss_strerror(ret)); + goto done; + } + + if (res->count == 0) { + *_out = NULL; + ret = EOK; + goto done; + } + + out = talloc_zero_array(tmp_ctx, const char *, res->count + 1); + if (out == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero_array() failed\n"); + ret = ENOMEM; + goto done; + } + + num_groups = 0; + for (i = 0; i < res->count; i++) { + gid = sss_view_ldb_msg_find_attr_as_uint64(domain, res->msgs[i], + SYSDB_GIDNUM, 0); + if (gid == 0 && domain->type == DOM_TYPE_POSIX) { + continue; + } + + out[num_groups] = ifp_groups_build_path_from_msg(out, + domain, + res->msgs[i]); + if (out[num_groups] == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "ifp_groups_build_path() failed\n"); + ret = ENOMEM; + goto done; + } + + num_groups++; + } + + *_out = talloc_steal(mem_ctx, out); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t +ifp_users_user_get_domainname(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ifp_ctx, + const char **_out) +{ + struct sss_domain_info *domain; + errno_t ret; + + ret = ifp_users_user_get(mem_ctx, sbus_req, ifp_ctx, &domain, NULL); + if (ret != EOK) { + return ret; + } + + *_out = domain->name; + + return EOK; +} + +errno_t +ifp_users_user_get_domain(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char **_out) +{ + const char *name; + const char *out; + errno_t ret; + + ret = ifp_users_user_get_domainname(NULL, sbus_req, ctx, &name); + if (ret != EOK) { + return ret; + } + + out = sbus_opath_compose(mem_ctx, IFP_PATH_DOMAINS, name); + if (out == NULL) { + return ENOMEM; + } + + *_out = out; + + return EOK; +} + +errno_t +ifp_users_user_get_extra_attributes(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ifp_ctx, + hash_table_t **_out) +{ + TALLOC_CTX *tmp_ctx; + struct sss_domain_info *domain; + struct ldb_message *base_user; + const char *name; + struct ldb_message **user; + struct ldb_message_element *el; + struct ldb_dn *basedn; + size_t count; + const char *filter; + const char **extra; + hash_table_t *table; + hash_key_t key; + hash_value_t value; + const char **values; + errno_t ret; + int hret; + int i; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + extra = ifp_get_user_extra_attributes(tmp_ctx, ifp_ctx); + if (extra == NULL || extra[0] == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "No extra attributes to return\n"); + *_out = NULL; + ret = EOK; + goto done; + } + + ret = ifp_users_user_get(tmp_ctx, sbus_req, ifp_ctx, &domain, &base_user); + if (ret != EOK) { + goto done; + } + + basedn = sysdb_user_base_dn(tmp_ctx, domain); + if (basedn == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_user_base_dn() failed\n"); + ret = ENOMEM; + goto done; + } + + name = ldb_msg_find_attr_as_string(base_user, SYSDB_NAME, NULL); + if (name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "A user with no name\n"); + ret = ERR_INTERNAL; + goto done; + } + + filter = talloc_asprintf(tmp_ctx, "(&(%s=%s)(%s=%s))", + SYSDB_OBJECTCATEGORY, SYSDB_USER_CLASS, + SYSDB_NAME, name); + if (filter == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf() failed\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_entry(tmp_ctx, domain->sysdb, basedn, + LDB_SCOPE_SUBTREE, filter, + extra, &count, &user); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to lookup user [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + if (count == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "User %s not found!\n", name); + ret = ENOENT; + goto done; + } else if (count > 1) { + DEBUG(SSSDBG_CRIT_FAILURE, "More than one entry found!\n"); + ret = ERR_INTERNAL; + goto done; + } + + ret = sss_hash_create(tmp_ctx, 0, &table); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create hash table!\n"); + goto done; + } + + /* Read each extra attribute. */ + for (i = 0; extra[i] != NULL; i++) { + el = ldb_msg_find_element(user[0], extra[i]); + if (el == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "Attribute %s not found, skipping...\n", + extra[i]); + continue; + } + + values = sss_ldb_el_to_string_list(table, el); + if (values == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_ldb_el_to_string_list() failed\n"); + ret = ENOMEM; + goto done; + } + + key.type = HASH_KEY_STRING; + key.str = talloc_strdup(table, extra[i]); + if (key.str == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup() failed\n"); + ret = ENOMEM; + goto done; + } + + value.type = HASH_VALUE_PTR; + value.ptr = values; + + hret = hash_enter(table, &key, &value); + if (hret != HASH_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to insert entry " + "into hash table: %d\n", hret); + ret = EIO; + goto done; + } + } + + *_out = talloc_steal(mem_ctx, table); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +ifp_cache_list_user(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char ***_out) +{ + return ifp_cache_list(mem_ctx, ctx, IFP_CACHE_USER, _out); +} + +errno_t +ifp_cache_list_by_domain_user(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char *domain, + const char ***_out) +{ + return ifp_cache_list_by_domain(mem_ctx, ctx, domain, IFP_CACHE_USER, _out); +} + +errno_t +ifp_cache_object_store_user(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + bool *_result) +{ + struct sss_domain_info *domain; + struct ldb_message *user; + errno_t ret; + + ret = ifp_users_user_get(NULL, sbus_req, ctx, &domain, &user); + if (ret != EOK) { + return ret; + } + + ret = ifp_cache_object_store(domain, user->dn); + talloc_free(user); + + if (ret == EOK) { + *_result = true; + } + + return ret; +} + +errno_t +ifp_cache_object_remove_user(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + bool *_result) +{ + struct sss_domain_info *domain; + struct ldb_message *user; + errno_t ret; + + ret = ifp_users_user_get(NULL, sbus_req, ctx, &domain, &user); + if (ret != EOK) { + return ret; + } + + ret = ifp_cache_object_remove(domain, user->dn); + talloc_free(user); + + if (ret == EOK) { + *_result = true; + } + + return ret; +} + +struct tevent_req * +ifp_users_list_by_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char *filter, + uint32_t limit) +{ + return ifp_users_list_by_attr_send(mem_ctx, ev, sbus_req, ctx, NULL, + filter, limit); +} diff --git a/src/responder/ifp/ifp_users.h b/src/responder/ifp/ifp_users.h new file mode 100644 index 0000000..f0f725c --- /dev/null +++ b/src/responder/ifp/ifp_users.h @@ -0,0 +1,254 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2015 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/>. +*/ + +#ifndef IFP_USERS_H_ +#define IFP_USERS_H_ + +#include "responder/ifp/ifp_private.h" + +/* Utility functions */ + +char * ifp_users_build_path_from_msg(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + struct ldb_message *msg); + +/* org.freedesktop.sssd.infopipe.Users */ + +struct tevent_req * +ifp_users_find_by_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char *name); + +errno_t +ifp_users_find_by_name_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char **_path); + +struct tevent_req * +ifp_users_find_by_id_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + uint32_t id); + +errno_t +ifp_users_find_by_id_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char **_path); + +struct tevent_req * +ifp_users_find_by_cert_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char *pem_cert); + +errno_t +ifp_users_find_by_cert_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char **_path); + +struct tevent_req * +ifp_users_list_by_cert_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char *pem_cert, + uint32_t limit); + +errno_t +ifp_users_list_by_cert_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char ***_paths); + +struct tevent_req * +ifp_users_find_by_name_and_cert_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char *name, + const char *pem_cert); + +errno_t +ifp_users_find_by_name_and_cert_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char **_path); + +struct tevent_req * +ifp_users_list_by_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char *filter, + uint32_t limit); + +errno_t +ifp_users_list_by_attr_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char ***_paths); + +struct tevent_req * +ifp_users_list_by_domain_and_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char *domain, + const char *filter, + uint32_t limit); + +errno_t +ifp_users_list_by_domain_and_name_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char ***_paths); + +struct tevent_req * +ifp_users_find_by_valid_cert_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char *pem_cert); + +errno_t +ifp_users_find_by_valid_cert_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char **_path); + +/* org.freedesktop.sssd.infopipe.Users.User */ + +struct tevent_req * +ifp_users_user_update_groups_list_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx); + +errno_t +ifp_users_user_update_groups_list_recv(struct tevent_req *req); + +errno_t +ifp_users_user_get_name(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char **_out); + +errno_t +ifp_users_user_get_uid_number(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + uint32_t *_out); + +errno_t +ifp_users_user_get_gid_number(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + uint32_t *_out); + +errno_t +ifp_users_user_get_gecos(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char **_out); + +errno_t +ifp_users_user_get_home_directory(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char **_out); + +errno_t +ifp_users_user_get_login_shell(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char **_out); + +errno_t +ifp_users_user_get_unique_id(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char **_out); + +errno_t +ifp_users_user_get_groups(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ifp_ctx, + const char ***_out); + +errno_t +ifp_users_user_get_domain(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char **_out); + +errno_t +ifp_users_user_get_domainname(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ifp_ctx, + const char **_out); + +errno_t +ifp_users_user_get_extra_attributes(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ifp_ctx, + hash_table_t **_out); + +/* org.freedesktop.sssd.infopipe.Cache */ + +errno_t +ifp_cache_list_user(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char ***_out); + +errno_t +ifp_cache_list_by_domain_user(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char *domain, + const char ***_out); + +/* org.freedesktop.sssd.infopipe.Cache.Object */ + +errno_t +ifp_cache_object_store_user(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + bool *_result); + +errno_t +ifp_cache_object_remove_user(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + bool *_result); + +struct tevent_req * +ifp_users_list_by_attr_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char *attr, + const char *filter, + uint32_t limit); + +errno_t +ifp_users_list_by_attr_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char ***_paths); +#endif /* IFP_USERS_H_ */ diff --git a/src/responder/ifp/ifpsrv.c b/src/responder/ifp/ifpsrv.c new file mode 100644 index 0000000..aaf8325 --- /dev/null +++ b/src/responder/ifp/ifpsrv.c @@ -0,0 +1,363 @@ +/* + Authors: + Jakub Hrozek <jhrozek@redhat.com> + + Copyright (C) 2013 Red Hat + + InfoPipe responder: the responder server + + 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 <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <string.h> +#include <sys/time.h> +#include <errno.h> +#include <popt.h> +#include <dbus/dbus.h> + +#include "util/util.h" +#include "util/strtonum.h" +#include "confdb/confdb.h" +#include "responder/ifp/ifp_private.h" +#include "responder/ifp/ifp_domains.h" +#include "responder/ifp/ifp_components.h" +#include "responder/ifp/ifp_iface/ifp_iface_async.h" +#include "sss_iface/sss_iface_async.h" + +#define DEFAULT_ALLOWED_UIDS "0" + +struct sss_cmd_table *get_ifp_cmds(void) +{ + static struct sss_cmd_table ifp_cmds[] = { + { SSS_GET_VERSION, sss_cmd_get_version }, + { SSS_CLI_NULL, NULL} + }; + + return ifp_cmds; +} + +static errno_t +sysbus_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *dbus_name, + struct ifp_ctx *ifp_ctx, + struct sbus_connection **_sysbus) +{ + struct sbus_connection *sysbus; + errno_t ret; + + sysbus = sbus_connect_system(mem_ctx, ev, dbus_name, + &ifp_ctx->rctx->last_request_time); + if (sysbus == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to connect to system bus!\n"); + return ERR_NO_SYSBUS; + } + + sbus_connection_set_access_check(sysbus, ifp_access_check, ifp_ctx); + + ret = ifp_register_sbus_interface(sysbus, ifp_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not register interfaces\n"); + goto done; + } + + ret = ifp_register_nodes(ifp_ctx, sysbus); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Could not register nodes factories\n"); + goto done; + } + + *_sysbus = sysbus; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(sysbus); + } + + return ret; +} + +static errno_t +ifp_sysbus_reconnect(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ifp_ctx) +{ + errno_t ret; + + DEBUG(SSSDBG_TRACE_FUNC, "Attempting to reconnect to the system bus\n"); + + if (ifp_ctx->sysbus != NULL) { + DEBUG(SSSDBG_TRACE_LIBS, "Already connected to sysbus\n"); + return EOK; + } + + /* Connect to the D-BUS system bus and set up methods */ + ret = sysbus_init(ifp_ctx, ifp_ctx->rctx->ev, IFP_BUS, + ifp_ctx, &ifp_ctx->sysbus); + if (ret == ERR_NO_SYSBUS) { + DEBUG(SSSDBG_MINOR_FAILURE, + "The system bus is not available..\n"); + return ret; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to connect to the system message bus\n"); + return ret; + } + + DEBUG(SSSDBG_TRACE_LIBS, "Reconnected to the system bus!\n"); + + return EOK; +} + +static errno_t +ifp_register_service_iface(struct ifp_ctx *ifp_ctx, + struct resp_ctx *rctx) +{ + errno_t ret; + + SBUS_INTERFACE(iface_svc, + sssd_service, + SBUS_METHODS( + SBUS_SYNC(METHOD, sssd_service, rotateLogs, responder_logrotate, rctx), + SBUS_SYNC(METHOD, sssd_service, sysbusReconnect, ifp_sysbus_reconnect, ifp_ctx) + ), + SBUS_SIGNALS(SBUS_NO_SIGNALS), + SBUS_PROPERTIES( + SBUS_SYNC(GETTER, sssd_service, debug_level, generic_get_debug_level, NULL), + SBUS_SYNC(SETTER, sssd_service, debug_level, generic_set_debug_level, NULL) + ) + ); + + ret = sbus_connection_add_path(rctx->mon_conn, SSS_BUS_PATH, &iface_svc); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to register service interface" + "[%d]: %s\n", ret, sss_strerror(ret)); + } + + return ret; +} + +int ifp_process_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct confdb_ctx *cdb) +{ + struct resp_ctx *rctx; + struct sss_cmd_table *ifp_cmds; + struct ifp_ctx *ifp_ctx; + int ret; + char *uid_str; + char *attr_list_str; + char *wildcard_limit_str; + char *endptr; + + ifp_cmds = get_ifp_cmds(); + ret = sss_process_init(mem_ctx, ev, cdb, + ifp_cmds, + NULL, -1, NULL, -1, + CONFDB_IFP_CONF_ENTRY, + SSS_BUS_IFP, SSS_IFP_SBUS_SERVICE_NAME, + sss_connection_setup, + &rctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "sss_process_init() failed\n"); + return ret; + } + + ifp_ctx = talloc_zero(rctx, struct ifp_ctx); + if (ifp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error initializing ifp_ctx\n"); + ret = ENOMEM; + goto fail; + } + + ifp_ctx->rctx = rctx; + ifp_ctx->rctx->pvt_ctx = ifp_ctx; + + ret = sss_names_init_from_args(ifp_ctx, + SSS_DEFAULT_RE, + "%1$s@%2$s", &ifp_ctx->snctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error initializing regex data\n"); + goto fail; + } + + ret = confdb_get_string(ifp_ctx->rctx->cdb, ifp_ctx->rctx, + CONFDB_IFP_CONF_ENTRY, CONFDB_SERVICE_ALLOWED_UIDS, + DEFAULT_ALLOWED_UIDS, &uid_str); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to get allowed UIDs.\n"); + goto fail; + } + + ret = csv_string_to_uid_array(ifp_ctx->rctx, uid_str, + &ifp_ctx->rctx->allowed_uids_count, + &ifp_ctx->rctx->allowed_uids); + talloc_free(uid_str); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to set allowed UIDs.\n"); + goto fail; + } + + ret = confdb_get_string(ifp_ctx->rctx->cdb, ifp_ctx->rctx, + CONFDB_IFP_CONF_ENTRY, CONFDB_IFP_USER_ATTR_LIST, + NULL, &attr_list_str); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to get user attribute list.\n"); + goto fail; + } + + ifp_ctx->user_whitelist = ifp_parse_user_attr_list(ifp_ctx, attr_list_str); + talloc_free(attr_list_str); + if (ifp_ctx->user_whitelist == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to parse the allowed attribute list\n"); + goto fail; + } + + /* A bit convoluted way until we have a confdb_get_uint32 */ + ret = confdb_get_string(ifp_ctx->rctx->cdb, + ifp_ctx->rctx, + CONFDB_IFP_CONF_ENTRY, + CONFDB_IFP_WILDCARD_LIMIT, + NULL, /* no limit by default */ + &wildcard_limit_str); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to retrieve limit for a wildcard search\n"); + goto fail; + } + + if (wildcard_limit_str) { + ifp_ctx->wildcard_limit = strtouint32(wildcard_limit_str, &endptr, 10); + if ((errno != 0) || *endptr || (wildcard_limit_str == endptr)) { + ret = errno ? errno : EINVAL; + goto fail; + } + } + + /* Connect to the D-BUS system bus and set up methods */ + ret = sysbus_init(ifp_ctx, ifp_ctx->rctx->ev, IFP_BUS, + ifp_ctx, &ifp_ctx->sysbus); + if (ret == ERR_NO_SYSBUS) { + DEBUG(SSSDBG_MINOR_FAILURE, + "The system bus is not available..\n"); + /* Explicitly ignore, the D-Bus daemon will start us */ + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to connect to the system message bus\n"); + talloc_free(ifp_ctx); + return EIO; + } + + ret = schedule_get_domains_task(rctx, rctx->ev, rctx, NULL, NULL, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "schedule_get_domains_tasks failed.\n"); + goto fail; + } + + /* The responder is initialized. Now tell it to the monitor. */ + ret = sss_monitor_service_init(rctx, rctx->ev, SSS_BUS_IFP, + SSS_IFP_SBUS_SERVICE_NAME, + SSS_IFP_SBUS_SERVICE_VERSION, + MT_SVC_SERVICE, + &rctx->last_request_time, &rctx->mon_conn); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error setting up message bus\n"); + goto fail; + } + + ret = ifp_register_service_iface(ifp_ctx, rctx); + if (ret != EOK) { + goto fail; + } + + DEBUG(SSSDBG_TRACE_FUNC, "InfoPipe Initialization complete\n"); + return EOK; + +fail: + talloc_free(rctx); + return ret; +} + +int main(int argc, const char *argv[]) +{ + int opt; + poptContext pc; + char *opt_logger = NULL; + struct main_context *main_ctx; + int ret; + uid_t uid = 0; + gid_t gid = 0; + + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_MAIN_OPTS + SSSD_LOGGER_OPTS + SSSD_SERVER_OPTS(uid, gid) + SSSD_RESPONDER_OPTS + POPT_TABLEEND + }; + + /* Set debug level to invalid value so we can decide if -d 0 was used. */ + debug_level = SSSDBG_INVALID; + + umask(DFL_RSP_UMASK); + + pc = poptGetContext(argv[0], argc, argv, long_options, 0); + while((opt = poptGetNextOpt(pc)) != -1) { + switch(opt) { + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + return 1; + } + } + + poptFreeContext(pc); + + /* set up things like debug, signals, daemonization, etc. */ + debug_log_file = "sssd_ifp"; + DEBUG_INIT(debug_level, opt_logger); + + ret = server_setup("ifp", true, 0, 0, 0, + CONFDB_IFP_CONF_ENTRY, &main_ctx, true); + if (ret != EOK) return 2; + + ret = die_if_parent_died(); + if (ret != EOK) { + /* This is not fatal, don't return */ + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not set up to exit when parent process does\n"); + } + + ret = ifp_process_init(main_ctx, + main_ctx->event_ctx, + main_ctx->confdb_ctx); + if (ret != EOK) return 3; + + /* loop on main */ + server_loop(main_ctx); + return 0; +} diff --git a/src/responder/ifp/ifpsrv_cmd.c b/src/responder/ifp/ifpsrv_cmd.c new file mode 100644 index 0000000..8cf1ec8 --- /dev/null +++ b/src/responder/ifp/ifpsrv_cmd.c @@ -0,0 +1,631 @@ +/* + Authors: + Jakub Hrozek <jhrozek@redhat.com> + + Copyright (C) 2013 Red Hat + + InfoPipe responder: the responder commands + + 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 "db/sysdb.h" + +#include "responder/ifp/ifp_private.h" +#include "responder/common/cache_req/cache_req.h" +#include "responder/ifp/ifp_iface/ifp_iface_async.h" + +struct ifp_user_get_attr_state { + const char **attrs; + struct ldb_result *res; + + enum sss_dp_acct_type search_type; + + struct sss_domain_info *dom; + + struct resp_ctx *rctx; + struct sss_nc_ctx *ncache; +}; + +static void ifp_user_get_attr_done(struct tevent_req *subreq); + +static struct tevent_req * +ifp_user_get_attr_send(TALLOC_CTX *mem_ctx, struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, + enum sss_dp_acct_type search_type, + const char *input, const char **attrs) +{ + errno_t ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct ifp_user_get_attr_state *state; + struct cache_req_data *data; + + req = tevent_req_create(mem_ctx, &state, struct ifp_user_get_attr_state); + if (req == NULL) { + return NULL; + } + state->attrs = attrs; + state->rctx = rctx; + state->ncache = ncache; + state->search_type = search_type; + + switch (state->search_type) { + case SSS_DP_USER: + data = cache_req_data_name(state, CACHE_REQ_USER_BY_NAME, input); + break; + case SSS_DP_INITGROUPS: + data = cache_req_data_name(state, CACHE_REQ_INITGROUPS, input); + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported search type [%d]!\n", + state->search_type); + ret = ERR_INTERNAL; + goto done; + } + + if (data == NULL) { + ret = ENOMEM; + goto done; + } + + /* IFP serves both POSIX and application domains. Requests that need + * to differentiate between the two must be qualified + */ + subreq = cache_req_send(state, state->rctx->ev, state->rctx, state->ncache, + 0, CACHE_REQ_ANY_DOM, NULL, data); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ifp_user_get_attr_done, req); + + ret = EOK; +done: + if (ret != EOK) { + tevent_req_error(req, ret); + tevent_req_post(req, rctx->ev); + } + return req; +} + +static void ifp_user_get_attr_done(struct tevent_req *subreq) +{ + struct ifp_user_get_attr_state *state = NULL; + struct tevent_req *req = NULL; + struct cache_req_result *result; + errno_t ret; + const char *fqdn; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ifp_user_get_attr_state); + + ret = cache_req_single_domain_recv(state, subreq, &result); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + state->res = talloc_steal(state, result->ldb_result); + state->dom = result->domain; + talloc_zfree(result); + + fqdn = ldb_msg_find_attr_as_string(state->res->msgs[0], SYSDB_NAME, NULL); + if (fqdn == NULL) { + tevent_req_error(req, ERR_INTERNAL); + return; + } + + if (state->search_type == SSS_DP_USER) { + /* throw away the result but keep the fqdn and perform attr search */ + fqdn = talloc_steal(state, fqdn); + talloc_zfree(state->res); + + ret = sysdb_get_user_attr_with_views(state, state->dom, fqdn, + state->attrs, &state->res); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_get_user_attr_with_views() " + "failed [%d]: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } else if (state->res->count == 0) { + tevent_req_error(req, ENOENT); + return; + } else if (state->res->count != 1) { + DEBUG(SSSDBG_CRIT_FAILURE, "sysdb_get_user_attr_with_views() " + "returned more than one result!\n"); + tevent_req_error(req, ENOENT); + return; + } + } + + tevent_req_done(req); +} + +static errno_t +ifp_user_get_attr_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ldb_result **_res, + struct sss_domain_info **_domain) +{ + struct ifp_user_get_attr_state *state = tevent_req_data(req, + struct ifp_user_get_attr_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (state->res == NULL) { + /* Did the request end with success but with no data? */ + return ENOENT; + } + + if (_res) { + *_res = talloc_steal(mem_ctx, state->res); + } + + if (_domain) { + *_domain = state->dom; + } + + return EOK; +} + +static errno_t +ifp_get_user_attr_write_reply(DBusMessageIter *iter, + const char **attrs, + struct resp_ctx *rctx, + struct sss_domain_info *domain, + struct ldb_result *res) +{ + struct ldb_message_element *el; + DBusMessageIter iter_dict; + dbus_bool_t dbret; + errno_t ret; + int ai; + + dbret = dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, + &iter_dict); + if (!dbret) { + return EIO; + } + + if (res->count > 0) { + ret = ifp_ldb_el_output_name(rctx, res->msgs[0], SYSDB_NAME, domain); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot convert SYSDB_NAME to output format [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = ifp_ldb_el_output_name(rctx, res->msgs[0], SYSDB_NAME_ALIAS, domain); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot convert SYSDB_NAME_ALIAS to output format [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + for (ai = 0; attrs != NULL && attrs[ai] != NULL; ai++) { + if (strcmp(attrs[ai], "domainname") == 0) { + ret = ifp_add_value_to_dict(&iter_dict, "domainname", + domain->name); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot add attribute domainname to message\n"); + continue; + } + } + + el = sss_view_ldb_msg_find_element(domain, res->msgs[0], attrs[ai]); + if (el == NULL || el->num_values == 0) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Attribute %s not present or has no values\n", + attrs[ai]); + continue; + } + + ret = ifp_add_ldb_el_to_dict(&iter_dict, el); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot add attribute %s to message\n", + attrs[ai]); + continue; + } + } + } + + dbret = dbus_message_iter_close_container(iter, &iter_dict); + if (!dbret) { + ret = EIO; + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + dbus_message_iter_abandon_container(iter, &iter_dict); + } + + return ret; +} + +struct ifp_get_user_attr_state { + const char *name; + const char **attrs; + struct resp_ctx *rctx; + + DBusMessageIter *write_iter; +}; + +static void ifp_get_user_attr_done(struct tevent_req *subreq); + +struct tevent_req * +ifp_get_user_attr_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char *name, + const char **attrs, + DBusMessageIter *write_iter) +{ + struct ifp_get_user_attr_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + DEBUG(SSSDBG_IMPORTANT_INFO, "GetUserAttr is deprecated, please consider " + "switching to org.freedesktop.sssd.infopipe.Users.User interface\n"); + + req = tevent_req_create(mem_ctx, &state, struct ifp_get_user_attr_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + state->name = name; + state->attrs = attrs; + state->rctx = ctx->rctx; + state->write_iter = write_iter; + + DEBUG(SSSDBG_FUNC_DATA, + "Looking up attributes of user [%s] on behalf of %"PRIi64"\n", + state->name, sbus_req->sender->uid); + + subreq = ifp_user_get_attr_send(state, ctx->rctx, ctx->rctx->ncache, + SSS_DP_USER, state->name, state->attrs); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ifp_get_user_attr_done, req); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void ifp_get_user_attr_done(struct tevent_req *subreq) +{ + struct ifp_get_user_attr_state *state; + struct sss_domain_info *dom; + struct ldb_result *res; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ifp_get_user_attr_state); + + ret = ifp_user_get_attr_recv(state, subreq, &res, &dom); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to get user attributes [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + ret = ifp_get_user_attr_write_reply(state->write_iter, state->attrs, + state->rctx, dom, res); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to construct reply [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +errno_t +ifp_get_user_attr_recv(TALLOC_CTX *mem_ctx, struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +static errno_t +ifp_user_get_groups_build_group_list(struct resp_ctx *rctx, + const char *name, + const char **groupnames, + int *gri) +{ + struct sized_string *group_name; + errno_t ret; + + ret = sized_domain_name(NULL, rctx, name, &group_name); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Unable to get sized name for %s [%d]: %s\n", + name, ret, sss_strerror(ret)); + goto done; + } + + groupnames[*gri] = talloc_strndup(groupnames, + group_name->str, + group_name->len); + talloc_free(group_name); + if (groupnames[*gri] == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "talloc_strndup failed\n"); + ret = ENOMEM; + goto done; + } + (*gri)++; + + DEBUG(SSSDBG_TRACE_FUNC, "Adding group %s\n", groupnames[*gri]); + +done: + return ret; +} + +static errno_t +ifp_user_get_groups_build_reply(TALLOC_CTX *mem_ctx, + struct resp_ctx *rctx, + struct sss_domain_info *domain, + struct ldb_result *res, + const char ***_groupnames) +{ + TALLOC_CTX *tmp_ctx = NULL; + int i, gri, num; + const char *name; + const char **groupnames; + gid_t orig_gid; + struct ldb_message *msg = NULL; + const char *attrs[] = {SYSDB_NAME, NULL}; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + num = res->count; + groupnames = talloc_zero_array(mem_ctx, const char *, num + 1); + if (groupnames == NULL) { + return ENOMEM; + } + + gri = 0; + orig_gid = sss_view_ldb_msg_find_attr_as_uint64(domain, + res->msgs[0], + SYSDB_PRIMARY_GROUP_GIDNUM, 0); + ret = sysdb_search_group_by_gid(tmp_ctx, domain, orig_gid, attrs, &msg); + + /* If origPrimaryGroupGidNumber exists add it to group list */ + if(ret == EOK) { + name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); + + if (name != NULL) { + ifp_user_get_groups_build_group_list(rctx, name, groupnames, &gri); + } + } + + /* Start counting from 1 to exclude the user entry */ + for (i = 1; i < num; i++) { + name = sss_view_ldb_msg_find_attr_as_string(domain, + res->msgs[i], + SYSDB_NAME, NULL); + if (name == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "Skipping a group with no name\n"); + continue; + } + + ifp_user_get_groups_build_group_list(rctx, name, groupnames, &gri); + } + + *_groupnames = groupnames; + + talloc_free(tmp_ctx); + return EOK; +} + +struct ifp_user_get_groups_state { + struct tevent_context *ev; + struct resp_ctx *rctx; + struct ldb_result *res; + struct sss_domain_info *domain; + const char **groupnames; +}; + +static void ifp_user_get_groups_attr_done(struct tevent_req *subreq); +static void ifp_user_get_groups_done(struct tevent_req *subreq); + +struct tevent_req * +ifp_user_get_groups_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char *name) +{ + const char *attrs[] = {SYSDB_MEMBEROF, NULL}; + struct ifp_user_get_groups_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ifp_user_get_groups_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + state->ev = ev; + state->rctx = ctx->rctx; + + DEBUG(SSSDBG_FUNC_DATA, + "Looking up groups of user [%s] on behalf of %"PRIi64"\n", + name, sbus_req->sender->uid); + + subreq = ifp_user_get_attr_send(state, ctx->rctx, ctx->rctx->ncache, + SSS_DP_INITGROUPS, name, attrs); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ifp_user_get_groups_attr_done, req); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void ifp_user_get_groups_attr_done(struct tevent_req *subreq) +{ + struct ifp_user_get_groups_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ifp_user_get_groups_state); + + ret = ifp_user_get_attr_recv(state, subreq, &state->res, &state->domain); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to get group members [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + subreq = resp_resolve_group_names_send(state, state->ev, state->rctx, + state->domain, state->res); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + tevent_req_error(req, ENOMEM); + return; + } + + tevent_req_set_callback(subreq, ifp_user_get_groups_done, req); +} + +static void ifp_user_get_groups_done(struct tevent_req *subreq) +{ + struct ifp_user_get_groups_state *state; + struct ldb_result *res; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct ifp_user_get_groups_state); + + ret = resp_resolve_group_names_recv(state, subreq, &res); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to resolve group names [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + res = res == NULL ? state->res : res; + + ret = ifp_user_get_groups_build_reply(state, state->rctx, state->domain, + res, &state->groupnames); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to construct reply [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t +ifp_user_get_groups_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char ***_groupnames) +{ + struct ifp_user_get_groups_state *state; + state = tevent_req_data(req, struct ifp_user_get_groups_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_groupnames = talloc_steal(mem_ctx, state->groupnames); + + return EOK; +} + +struct cli_protocol_version *register_cli_protocol_version(void) +{ + static struct cli_protocol_version ssh_cli_protocol_version[] = { + {0, NULL, NULL} + }; + + return ssh_cli_protocol_version; +} + +errno_t +ifp_ping(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct ifp_ctx *ctx, + const char *ping, + const char **_pong) +{ + DEBUG(SSSDBG_CONF_SETTINGS, "Got request for [%s]\n", ping); + + if (strcasecmp(ping, "ping") != 0) { + DEBUG(SSSDBG_OP_FAILURE, "Ping() only accepts \"ping\" as a param\n"); + return EINVAL; + } + + *_pong = "PONG"; + + return EOK; +} diff --git a/src/responder/ifp/ifpsrv_util.c b/src/responder/ifp/ifpsrv_util.c new file mode 100644 index 0000000..c21ceef --- /dev/null +++ b/src/responder/ifp/ifpsrv_util.c @@ -0,0 +1,455 @@ +/* + Authors: + Jakub Hrozek <jhrozek@redhat.com> + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2013 Red Hat + + InfoPipe responder: Utility functions + + 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 <sys/param.h> + +#include "db/sysdb.h" +#include "responder/ifp/ifp_private.h" + +#define IFP_USER_DEFAULT_ATTRS {SYSDB_NAME, SYSDB_UIDNUM, \ + SYSDB_GIDNUM, SYSDB_GECOS, \ + SYSDB_HOMEDIR, SYSDB_SHELL, \ + "groups", "domain", "domainname", \ + "extraAttributes", NULL} + +errno_t ifp_add_value_to_dict(DBusMessageIter *iter_dict, + const char *key, + const char *value) +{ + DBusMessageIter iter_dict_entry; + DBusMessageIter iter_dict_val; + DBusMessageIter iter_array; + dbus_bool_t dbret; + + if (value == NULL || key == NULL) { + return EINVAL; + } + + dbret = dbus_message_iter_open_container(iter_dict, + DBUS_TYPE_DICT_ENTRY, NULL, + &iter_dict_entry); + if (!dbret) { + return ENOMEM; + } + + /* Start by appending the key */ + dbret = dbus_message_iter_append_basic(&iter_dict_entry, + DBUS_TYPE_STRING, &key); + if (!dbret) { + return ENOMEM; + } + + dbret = dbus_message_iter_open_container(&iter_dict_entry, + DBUS_TYPE_VARIANT, + DBUS_TYPE_ARRAY_AS_STRING + DBUS_TYPE_STRING_AS_STRING, + &iter_dict_val); + if (!dbret) { + return ENOMEM; + } + + /* Open container for values */ + dbret = dbus_message_iter_open_container(&iter_dict_val, + DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, + &iter_array); + if (!dbret) { + return ENOMEM; + } + + dbret = dbus_message_iter_append_basic(&iter_array, + DBUS_TYPE_STRING, + &value); + if (!dbret) { + return ENOMEM; + } + + dbret = dbus_message_iter_close_container(&iter_dict_val, + &iter_array); + if (!dbret) { + return ENOMEM; + } + + dbret = dbus_message_iter_close_container(&iter_dict_entry, + &iter_dict_val); + if (!dbret) { + return ENOMEM; + } + + dbret = dbus_message_iter_close_container(iter_dict, + &iter_dict_entry); + if (!dbret) { + return ENOMEM; + } + + return EOK; +} + +errno_t ifp_add_ldb_el_to_dict(DBusMessageIter *iter_dict, + struct ldb_message_element *el) +{ + DBusMessageIter iter_dict_entry; + DBusMessageIter iter_dict_val; + DBusMessageIter iter_array; + dbus_bool_t dbret; + unsigned int i; + + if (el == NULL) { + return EINVAL; + } + + dbret = dbus_message_iter_open_container(iter_dict, + DBUS_TYPE_DICT_ENTRY, NULL, + &iter_dict_entry); + if (!dbret) { + return ENOMEM; + } + + /* Start by appending the key */ + dbret = dbus_message_iter_append_basic(&iter_dict_entry, + DBUS_TYPE_STRING, &(el->name)); + if (!dbret) { + return ENOMEM; + } + + dbret = dbus_message_iter_open_container(&iter_dict_entry, + DBUS_TYPE_VARIANT, + DBUS_TYPE_ARRAY_AS_STRING + DBUS_TYPE_STRING_AS_STRING, + &iter_dict_val); + if (!dbret) { + return ENOMEM; + } + + /* Open container for values */ + dbret = dbus_message_iter_open_container(&iter_dict_val, + DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, + &iter_array); + if (!dbret) { + return ENOMEM; + } + + /* Now add all the values */ + for (i = 0; i < el->num_values; i++) { + DEBUG(SSSDBG_TRACE_FUNC, "element [%s] has value [%s]\n", + el->name, (const char *) el->values[i].data); + + dbret = dbus_message_iter_append_basic(&iter_array, + DBUS_TYPE_STRING, + &(el->values[i].data)); + if (!dbret) { + return ENOMEM; + } + } + + dbret = dbus_message_iter_close_container(&iter_dict_val, + &iter_array); + if (!dbret) { + return ENOMEM; + } + + dbret = dbus_message_iter_close_container(&iter_dict_entry, + &iter_dict_val); + if (!dbret) { + return ENOMEM; + } + + dbret = dbus_message_iter_close_container(iter_dict, + &iter_dict_entry); + if (!dbret) { + return ENOMEM; + } + + return EOK; +} + + +bool +ifp_attr_allowed(const char *whitelist[], const char *attr) +{ + size_t i; + + if (whitelist == NULL) { + return false; + } + + for (i = 0; whitelist[i]; i++) { + if (strcasecmp(whitelist[i], attr) == 0) { + break; + } + } + + return (whitelist[i]) ? true : false; +} + +const char ** +ifp_parse_user_attr_list(TALLOC_CTX *mem_ctx, const char *csv) +{ + static const char *defaults[] = IFP_USER_DEFAULT_ATTRS; + + return parse_attr_list_ex(mem_ctx, csv, defaults); +} + +const char ** +ifp_get_user_extra_attributes(TALLOC_CTX *mem_ctx, struct ifp_ctx *ifp_ctx) +{ + TALLOC_CTX *tmp_ctx = NULL; + const char *std[] = IFP_USER_DEFAULT_ATTRS; + const char **whitelist = ifp_ctx->user_whitelist; + const char **extra; + bool found; + int extra_num; + int i, j; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return NULL; + } + + for (i = 0; whitelist[i] != NULL; i++) { + /* Just count number of attributes in whitelist. */ + } + + extra = talloc_zero_array(tmp_ctx, const char *, i + 1); + if (extra == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero_array() failed\n"); + goto fail; + } + + extra_num = 0; + for (i = 0; whitelist[i] != NULL; i++) { + found = false; + for (j = 0; std[j] != NULL; j++) { + if (strcmp(whitelist[i], std[j]) == 0) { + found = true; + break; + } + } + + if (!found) { + extra[extra_num] = talloc_strdup(extra, whitelist[i]); + if (extra[extra_num] == NULL) { + goto fail; + } + + extra_num++; + } + } + + extra = talloc_realloc(tmp_ctx, extra, const char *, extra_num + 1); + if (extra == NULL) { + goto fail; + } + + talloc_steal(mem_ctx, extra); + talloc_free(tmp_ctx); + return extra; + +fail: + talloc_free(tmp_ctx); + return NULL; +} + +bool +ifp_is_user_attr_allowed(struct ifp_ctx *ifp_ctx, const char *attr) +{ + return ifp_attr_allowed(ifp_ctx->user_whitelist, attr); +} + +static uint32_t ifp_list_limit(struct ifp_ctx *ctx, uint32_t limit) +{ + if (limit == 0) { + return ctx->wildcard_limit; + } else if (ctx->wildcard_limit) { + return MIN(ctx->wildcard_limit, limit); + } else { + return limit; + } +} + +struct ifp_list_ctx *ifp_list_ctx_new(TALLOC_CTX *mem_ctx, + struct ifp_ctx *ctx, + const char *attr, + const char *filter, + uint32_t limit) +{ + struct ifp_list_ctx *list_ctx; + + list_ctx = talloc_zero(mem_ctx, struct ifp_list_ctx); + if (list_ctx == NULL) { + return NULL; + } + + list_ctx->limit = ifp_list_limit(ctx, limit); + list_ctx->ctx = ctx; + list_ctx->dom = ctx->rctx->domains; + list_ctx->attr = attr; + list_ctx->filter = filter; + list_ctx->paths_max = 1; + list_ctx->paths = talloc_zero_array(list_ctx, const char *, + list_ctx->paths_max + 1); + if (list_ctx->paths == NULL) { + talloc_free(list_ctx); + return NULL; + } + + return list_ctx; +} + +errno_t ifp_list_ctx_remaining_capacity(struct ifp_list_ctx *list_ctx, + size_t entries, + size_t *_capacity) +{ + size_t capacity = list_ctx->limit - list_ctx->path_count; + errno_t ret; + size_t c; + + if (list_ctx->limit == 0) { + capacity = entries; + goto immediately; + } + + if (capacity < entries) { + DEBUG(SSSDBG_MINOR_FAILURE, + "IFP list request has limit of %"PRIu32" entries but back end " + "returned %zu entries\n", list_ctx->limit, + list_ctx->path_count + entries); + } else { + capacity = entries; + } + +immediately: + list_ctx->paths_max = list_ctx->path_count + capacity; + list_ctx->paths = talloc_realloc(list_ctx, list_ctx->paths, const char *, + list_ctx->paths_max + 1); + if (list_ctx->paths == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_realloc() failed\n"); + ret = ENOMEM; + goto done; + } + for (c = list_ctx->path_count; c <= list_ctx->paths_max; c++) { + list_ctx->paths[c] = NULL; + } + + *_capacity = capacity; + ret = EOK; + +done: + return ret; +} + +errno_t ifp_ldb_el_output_name(struct resp_ctx *rctx, + struct ldb_message *msg, + const char *el_name, + struct sss_domain_info *dom) +{ + struct ldb_message_element *el; + char *in_name; + char *out_name; + errno_t ret; + char *name; + TALLOC_CTX *tmp_ctx; + + el = ldb_msg_find_element(msg, el_name); + if (el == NULL) { + return EOK; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + for (size_t c = 0; c < el->num_values; c++) { + in_name = (char *) el->values[c].data; + ret = sss_parse_internal_fqname(tmp_ctx, in_name, &name, NULL); + if (ret != EOK) { + goto done; + } + + out_name = sss_output_name(tmp_ctx, in_name, dom->case_preserve, + rctx->override_space); + if (out_name == NULL) { + ret = EIO; + goto done; + } + + if (dom->fqnames) { + out_name = sss_tc_fqname(tmp_ctx, dom->names, dom, out_name); + if (out_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_tc_fqname failed\n"); + ret = ENOMEM; + goto done; + } + } + + talloc_free(el->values[c].data); + el->values[c].data = (uint8_t *) talloc_steal(el->values, out_name); + el->values[c].length = strlen(out_name); + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +char *ifp_format_name_attr(TALLOC_CTX *mem_ctx, struct ifp_ctx *ifp_ctx, + const char *in_name, struct sss_domain_info *dom) +{ + TALLOC_CTX *tmp_ctx; + char *out_name; + char *ret_name = NULL; + char *shortname; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return NULL; + } + + ret = sss_parse_internal_fqname(tmp_ctx, in_name, &shortname, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unparseable name %s\n", in_name); + goto done; + } + + out_name = sss_output_name(tmp_ctx, in_name, dom->case_preserve, + ifp_ctx->rctx->override_space); + if (out_name == NULL) { + goto done; + } + + if (dom->fqnames) { + out_name = sss_tc_fqname(tmp_ctx, dom->names, dom, out_name); + if (out_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_tc_fqname failed\n"); + goto done; + } + } + + ret_name = talloc_steal(mem_ctx, out_name); +done: + talloc_free(tmp_ctx); + return ret_name; +} diff --git a/src/responder/ifp/org.freedesktop.sssd.infopipe.conf b/src/responder/ifp/org.freedesktop.sssd.infopipe.conf new file mode 100644 index 0000000..4437fb3 --- /dev/null +++ b/src/responder/ifp/org.freedesktop.sssd.infopipe.conf @@ -0,0 +1,47 @@ +<?xml version="1.0"?> <!--*-nxml-*--> +<!DOCTYPE busconfig PUBLIC + "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> +<busconfig> + + <!-- This configuration file specifies the required security policies + for the SSSD InfoPipe to work. --> + + <!-- Only root can own (provide) the SSSD service --> + <policy user="root"> + <allow own="org.freedesktop.sssd.infopipe"/> + </policy> + + <!-- Allow all methods on the interface --> + <!-- Right now, this will be handled by a limited ACL + within the InfoPipe Daemon. --> + <policy context="default"> + <allow send_destination="org.freedesktop.sssd.infopipe" + send_interface="org.freedesktop.DBus.Introspectable"/> + + <allow send_destination="org.freedesktop.sssd.infopipe" + send_interface="org.freedesktop.DBus.Properties" + send_member="GetAll"/> + <allow send_destination="org.freedesktop.sssd.infopipe" + send_interface="org.freedesktop.DBus.Properties" + send_member="Get"/> + <allow send_destination="org.freedesktop.sssd.infopipe" + send_interface="org.freedesktop.DBus.Properties" + send_member="Set"/> + + <allow send_interface="org.freedesktop.sssd.infopipe"/> + <allow send_interface="org.freedesktop.sssd.infopipe.Domains"/> + <allow send_interface="org.freedesktop.sssd.infopipe.Domains.Domain"/> + <allow send_interface="org.freedesktop.sssd.infopipe.Users"/> + <allow send_interface="org.freedesktop.sssd.infopipe.Users.User"/> + <allow send_interface="org.freedesktop.sssd.infopipe.Groups"/> + <allow send_interface="org.freedesktop.sssd.infopipe.Groups.Group"/> + <allow send_interface="org.freedesktop.sssd.infopipe.Cache"/> + <allow send_interface="org.freedesktop.sssd.infopipe.Cache.Object"/> + </policy> + + <policy user="root"> + <allow send_interface="org.freedesktop.sssd.infopipe.Components"/> + </policy> + +</busconfig> diff --git a/src/responder/ifp/org.freedesktop.sssd.infopipe.service b/src/responder/ifp/org.freedesktop.sssd.infopipe.service new file mode 100644 index 0000000..b5d5435 --- /dev/null +++ b/src/responder/ifp/org.freedesktop.sssd.infopipe.service @@ -0,0 +1,6 @@ +[D-BUS Service] +Name=org.freedesktop.sssd.infopipe +# "sss_signal" is used to force SSSD monitor to trigger "sssd_ifp" reconnection to dbus +Exec=/usr/local/libexec/sssd/sss_signal +User=root + diff --git a/src/responder/ifp/org.freedesktop.sssd.infopipe.service.in b/src/responder/ifp/org.freedesktop.sssd.infopipe.service.in new file mode 100644 index 0000000..ca06cb1 --- /dev/null +++ b/src/responder/ifp/org.freedesktop.sssd.infopipe.service.in @@ -0,0 +1,6 @@ +[D-BUS Service] +Name=org.freedesktop.sssd.infopipe +@ifp_dbus_exec_comment@ +Exec=@ifp_exec_cmd@ +User=root +@ifp_systemdservice@ diff --git a/src/responder/kcm/kcm.c b/src/responder/kcm/kcm.c new file mode 100644 index 0000000..f61d478 --- /dev/null +++ b/src/responder/kcm/kcm.c @@ -0,0 +1,374 @@ +/* + SSSD + + KCM Server - the mainloop and server setup + + Copyright (C) Red Hat, 2016 + + 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 "config.h" + +#include <popt.h> + +#include "responder/kcm/kcmsrv_ccache.h" +#include "responder/kcm/kcmsrv_pvt.h" +#include "responder/kcm/kcm_renew.h" +#include "responder/common/responder.h" +#include "providers/krb5/krb5_common.h" +#include "util/util.h" +#include "util/sss_krb5.h" + +#define DEFAULT_KCM_FD_LIMIT 2048 +#define DEFAULT_KCM_CLI_IDLE_TIMEOUT 300 + +#ifndef SSS_KCM_SOCKET_NAME +#define SSS_KCM_SOCKET_NAME DEFAULT_KCM_SOCKET_PATH +#endif + +static int kcm_responder_ctx_destructor(void *ptr) +{ + struct resp_ctx *rctx = talloc_get_type(ptr, struct resp_ctx); + + /* mark that we are shutting down the responder, so it is propagated + * into underlying contexts that are freed right before rctx */ + DEBUG(SSSDBG_TRACE_FUNC, "Responder is being shut down\n"); + rctx->shutting_down = true; + + return 0; +} + +static errno_t kcm_renewals_init(struct tevent_context *ev, + struct resp_ctx *rctx, + struct kcm_ctx *kctx, + struct krb5_ctx *krb5_ctx, + time_t renew_intv, + bool *_renewal_enabled) +{ +#ifndef HAVE_KCM_RENEWAL + return EOK; +#else + errno_t ret; + + ret = kcm_get_renewal_config(kctx, &krb5_ctx, &renew_intv); + if (ret == ENOTSUP) { + DEBUG(SSSDBG_TRACE_FUNC, "TGT Renewals support disabled\n"); + return EOK; + } else if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to read TGT renewal configuration [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + if (renew_intv > 0) { + *_renewal_enabled = true; + + ret = kcm_renewal_setup(rctx, krb5_ctx, ev, kctx->kcm_data->db, renew_intv); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Unable to setup TGT renewals [%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + } + + return EOK; +#endif +} + +static errno_t kcm_get_ccdb_be(struct kcm_ctx *kctx) +{ + errno_t ret; + char *str_db; + + ret = confdb_get_string(kctx->rctx->cdb, + kctx->rctx, + kctx->rctx->confdb_service_path, + CONFDB_KCM_DB, + "secdb", + &str_db); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get the KCM database type [%d]: %s\n", + ret, strerror(ret)); + return ret; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "KCM database type: %s\n", str_db); + if (strcasecmp(str_db, "memory") == 0) { + kctx->cc_be = CCDB_BE_MEMORY; + } else if (strcasecmp(str_db, "secdb") == 0) { + kctx->cc_be = CCDB_BE_SECDB; + } else { + DEBUG(SSSDBG_FATAL_FAILURE, "Unexpected KCM database type %s\n", str_db); + } + + return EOK; +} + +static int kcm_get_config(struct kcm_ctx *kctx) +{ + int ret; + char *sock_name; + + ret = confdb_get_int(kctx->rctx->cdb, + CONFDB_KCM_CONF_ENTRY, + CONFDB_SERVICE_FD_LIMIT, + DEFAULT_KCM_FD_LIMIT, + &kctx->fd_limit); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to get file descriptors limit\n"); + goto done; + } + + ret = confdb_get_int(kctx->rctx->cdb, + kctx->rctx->confdb_service_path, + CONFDB_RESPONDER_CLI_IDLE_TIMEOUT, + DEFAULT_KCM_CLI_IDLE_TIMEOUT, + &kctx->rctx->client_idle_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get the client idle timeout [%s] [%d]: %s\n", + CONFDB_RESPONDER_CLI_IDLE_TIMEOUT, ret, strerror(ret)); + goto done; + } + + /* Ensure that the client timeout is at least ten seconds */ + if (kctx->rctx->client_idle_timeout < 10) { + kctx->rctx->client_idle_timeout = 10; + } + + ret = confdb_get_string(kctx->rctx->cdb, + kctx->rctx, + kctx->rctx->confdb_service_path, + CONFDB_KCM_SOCKET, + SSS_KCM_SOCKET_NAME, + &sock_name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get KCM socket path [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + kctx->rctx->sock_name = sock_name; + + ret = kcm_get_ccdb_be(kctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get KCM ccache DB [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + + kctx->qctx = kcm_ops_queue_create(kctx, kctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot create KCM request queue [%d]: %s\n", + ret, strerror(ret)); + goto done; + } + ret = EOK; +done: + return ret; +} + +static int kcm_data_destructor(void *ptr) +{ + struct kcm_resp_ctx *kcm_data = talloc_get_type(ptr, struct kcm_resp_ctx); + + if (kcm_data != NULL) { + krb5_free_context(kcm_data->k5c); + } + return 0; +} + +static struct kcm_resp_ctx *kcm_data_setup(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct confdb_ctx *cdb, + const char *confdb_service_path, + enum kcm_ccdb_be cc_be) +{ + struct kcm_resp_ctx *kcm_data; + krb5_error_code kret; + + kcm_data = talloc_zero(mem_ctx, struct kcm_resp_ctx); + if (kcm_data == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error initializing kcm data\n"); + return NULL; + } + + kcm_data->db = kcm_ccdb_init(kcm_data, + ev, + cdb, + confdb_service_path, + cc_be); + if (kcm_data->db == NULL) { + talloc_free(kcm_data); + return NULL; + } + + kret = sss_krb5_init_context(&kcm_data->k5c); + if (kret != EOK) { + talloc_free(kcm_data); + return NULL; + } + talloc_set_destructor((TALLOC_CTX*)kcm_data, kcm_data_destructor); + + return kcm_data; +} + +static int kcm_process_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct confdb_ctx *cdb) +{ + struct resp_ctx *rctx; + struct kcm_ctx *kctx; + bool renewal_enabled = false; + struct krb5_ctx *krb5_ctx = NULL; + time_t renew_intv = 0; + int ret; + + rctx = talloc_zero(mem_ctx, struct resp_ctx); + if (rctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error initializing resp_ctx\n"); + return ENOMEM; + } + rctx->ev = ev; + rctx->cdb = cdb; + rctx->confdb_service_path = CONFDB_KCM_CONF_ENTRY; + rctx->shutting_down = false; + rctx->lfd = -1; + rctx->priv_lfd = -1; + + talloc_set_destructor((TALLOC_CTX*)rctx, kcm_responder_ctx_destructor); + + kctx = talloc_zero(rctx, struct kcm_ctx); + if (kctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error initializing kcm_ctx\n"); + ret = ENOMEM; + goto fail; + } + + kctx->rctx = rctx; + kctx->rctx->pvt_ctx = kctx; + + ret = kcm_get_config(kctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error getting KCM config\n"); + goto fail; + } + + kctx->kcm_data = kcm_data_setup(kctx, + ev, + kctx->rctx->cdb, + kctx->rctx->confdb_service_path, + kctx->cc_be); + if (kctx->kcm_data == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, + "fatal error initializing responder data\n"); + ret = EIO; + goto fail; + } + + ret = kcm_renewals_init(ev, rctx, kctx, krb5_ctx, renew_intv, &renewal_enabled); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to initialize TGT Renewals [%d]: %s\n", + ret, sss_strerror(ret)); + goto fail; + } + + if (renewal_enabled) { + /* Disable resp idle timeout to allow renewals */ + rctx->idle_timeout = 0; + } else { + responder_setup_idle_timeout_config(kctx->rctx); + } + + /* Set up file descriptor limits */ + responder_set_fd_limit(kctx->fd_limit); + + ret = activate_unix_sockets(rctx, kcm_connection_setup); + if (ret != EOK) goto fail; + + DEBUG(SSSDBG_TRACE_FUNC, "KCM Initialization complete\n"); + + return EOK; + +fail: + talloc_free(rctx); + return ret; +} + +int main(int argc, const char *argv[]) +{ + int opt; + poptContext pc; + char *opt_logger = NULL; + struct main_context *main_ctx; + int ret; + uid_t uid = 0; + gid_t gid = 0; + + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_MAIN_OPTS + SSSD_LOGGER_OPTS + SSSD_SERVER_OPTS(uid, gid) + POPT_TABLEEND + }; + + /* Set debug level to invalid value so we can decide if -d 0 was used. */ + debug_level = SSSDBG_INVALID; + + umask(DFL_RSP_UMASK); + + pc = poptGetContext(argv[0], argc, argv, long_options, 0); + while((opt = poptGetNextOpt(pc)) != -1) { + switch(opt) { + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + return 1; + } + } + + poptFreeContext(pc); + + /* set up things like debug, signals, daemonization, etc. */ + debug_log_file = "sssd_kcm"; + DEBUG_INIT(debug_level, opt_logger); + + ret = server_setup("kcm", true, 0, uid, gid, CONFDB_KCM_CONF_ENTRY, + &main_ctx, true); + if (ret != EOK) return 2; + + ret = die_if_parent_died(); + if (ret != EOK) { + /* This is not fatal, don't return */ + DEBUG(SSSDBG_OP_FAILURE, + "Could not set up to exit when parent process does\n"); + } + + ret = kcm_process_init(main_ctx, + main_ctx->event_ctx, + main_ctx->confdb_ctx); + if (ret != EOK) return 3; + + /* loop on main */ + server_loop(main_ctx); + + return 0; +} diff --git a/src/responder/kcm/kcm_renew.c b/src/responder/kcm/kcm_renew.c new file mode 100644 index 0000000..39e9470 --- /dev/null +++ b/src/responder/kcm/kcm_renew.c @@ -0,0 +1,775 @@ +/* + SSSD + + KCM Kerberos renewals -- Renew a TGT automatically + + Authors: + Justin Stephenson <jstephen@redhat.com> + + Copyright (C) 2020 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 "util/util.h" +#include "providers/krb5/krb5_common.h" +#include "providers/krb5/krb5_auth.h" +#include "providers/krb5/krb5_utils.h" +#include "providers/krb5/krb5_ccache.h" +#include "responder/kcm/kcmsrv_ccache.h" +#include "responder/kcm/kcmsrv_pvt.h" +#include "responder/kcm/kcmsrv_ccache_pvt.h" +#include "responder/kcm/kcm_renew.h" + +extern struct dp_option default_krb5_opts[]; + +struct kcm_renew_auth_ctx { + struct tevent_context *ev; + struct krb5child_req *kr; + + struct krb5_ctx *krb5_ctx; + struct kcm_auth_data *auth_data; + + uint8_t *buf; + ssize_t len; +}; + +struct kcm_auth_data { + struct kcm_renew_auth_ctx *auth_ctx; + struct krb5_ctx *krb5_ctx; + uid_t uid; + gid_t gid; + const char *ccname; + const char *upn; +}; + +static void kcm_renew_tgt_done(struct tevent_req *req); + +static errno_t kcm_set_options(struct krb5_ctx *krb5_ctx, + char *lifetime, + char *rtime, + bool validate, + bool canonicalize, + int timeout, + char *renew_intv, + time_t *_renew_intv_tm) +{ + errno_t ret; + krb5_error_code kerr; + krb5_deltat renew_interval_delta; + + if (renew_intv != NULL) { + kerr = krb5_string_to_deltat(renew_intv, &renew_interval_delta); + if (kerr != 0) { + DEBUG(SSSDBG_FATAL_FAILURE, "krb5_string_to_deltat failed\n"); + ret = ENOMEM; + goto done; + } + + *_renew_intv_tm = renew_interval_delta; + } else { + *_renew_intv_tm = 0; + } + DEBUG(SSSDBG_TRACE_FUNC, "Option [%s] set to [%s]\n", + CONFDB_KCM_KRB5_RENEW_INTERVAL, + renew_intv == NULL ? "none" : renew_intv); + + if (lifetime != NULL) { + ret = krb5_string_to_deltat(lifetime, &krb5_ctx->lifetime); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to convert lifetime string [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + krb5_ctx->lifetime_str = lifetime; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Option [%s] set to [%s]\n", + CONFDB_KCM_KRB5_LIFETIME, + krb5_ctx->lifetime_str == NULL ? "none" : krb5_ctx->lifetime_str); + + if (rtime != 0) { + ret = krb5_string_to_deltat(rtime, &krb5_ctx->rlife); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to convert renewable lifetime " + "string [%d]: %s.\n", ret, sss_strerror(ret)); + goto done; + } + } + DEBUG(SSSDBG_TRACE_FUNC, "Option [%s] set to [%s]\n", + CONFDB_KCM_KRB5_RENEWABLE_LIFETIME, + rtime == NULL ? "none" : rtime); + + ret = dp_opt_set_bool(krb5_ctx->opts, KRB5_VALIDATE, validate); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot set krb5 child timeout [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Option [%s] set to [%s]\n", + CONFDB_KCM_KRB5_VALIDATE, + validate ? "true" : "false"); + + krb5_ctx->canonicalize = canonicalize; + DEBUG(SSSDBG_TRACE_FUNC, "Option [%s] set to [%s]\n", + CONFDB_KCM_KRB5_CANONICALIZE, + canonicalize ? "true" : "false"); + + if (timeout > 0) { + ret = dp_opt_set_int(krb5_ctx->opts, KRB5_AUTH_TIMEOUT, timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot set krb5 child timeout [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + } + + DEBUG(SSSDBG_TRACE_FUNC, "Option [%s] set to [%d]\n", + CONFDB_KCM_KRB5_AUTH_TIMEOUT, + timeout); + + ret = EOK; +done: + return ret; +} + +static errno_t kcm_read_options(TALLOC_CTX *mem_ctx, + struct confdb_ctx *cdb, + const char *cpath, + char **_lifetime, + char **_rtime, + bool *_validate, + bool *_canonicalize, + int *_timeout, + char **_renew_intv) +{ + TALLOC_CTX *tmp_ctx; + char *lifetime; + char *rtime; + bool validate; + bool canonicalize; + int timeout; + char *renew_intv; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = confdb_get_string(cdb, tmp_ctx, cpath, + CONFDB_KCM_KRB5_LIFETIME, NULL, + &lifetime); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot read %s/%s [%d]: %s\n", cpath, + CONFDB_KCM_KRB5_LIFETIME, ret, sss_strerror(ret)); + goto done; + } + + ret = confdb_get_string(cdb, tmp_ctx, cpath, + CONFDB_KCM_KRB5_RENEWABLE_LIFETIME, NULL, + &rtime); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot read %s/%s [%d]: %s\n", cpath, + CONFDB_KCM_KRB5_RENEWABLE_LIFETIME, ret, sss_strerror(ret)); + goto done; + } + + ret = confdb_get_bool(cdb, cpath, + CONFDB_KCM_KRB5_VALIDATE, false, + &validate); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot read %s/%s [%d]: %s\n", cpath, + CONFDB_KCM_KRB5_VALIDATE, ret, sss_strerror(ret)); + goto done; + } + + ret = confdb_get_bool(cdb, cpath, + CONFDB_KCM_KRB5_CANONICALIZE, false, + &canonicalize); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot read %s/%s [%d]: %s\n", cpath, + CONFDB_KCM_KRB5_CANONICALIZE, ret, sss_strerror(ret)); + goto done; + } + + ret = confdb_get_int(cdb, cpath, + CONFDB_KCM_KRB5_AUTH_TIMEOUT, 0, + &timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot read %s/%s [%d]: %s\n", cpath, + CONFDB_KCM_KRB5_AUTH_TIMEOUT, ret, sss_strerror(ret)); + goto done; + } + + ret = confdb_get_string(cdb, tmp_ctx, cpath, + CONFDB_KCM_KRB5_RENEW_INTERVAL, NULL, + &renew_intv); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot read %s/%s [%d]: %s\n", cpath, + CONFDB_KCM_KRB5_AUTH_TIMEOUT, ret, sss_strerror(ret)); + goto done; + } + + + *_lifetime = talloc_steal(mem_ctx, lifetime); + *_rtime = talloc_steal(mem_ctx, rtime); + *_validate = validate; + *_canonicalize = canonicalize; + *_timeout = timeout; + *_renew_intv = renew_intv; + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +int kcm_get_renewal_config(struct kcm_ctx *kctx, + struct krb5_ctx **_krb5_ctx, + time_t *_renew_intv) +{ + int ret; + struct krb5_ctx *krb5_ctx; + char *lifetime; + char *rtime; + bool validate; + bool canonicalize; + int timeout; + char *renew_intv; + time_t renew_intv_tm; + bool tgt_renewal; + char *tgt_renewal_inherit; + const char *conf_path; + int i; + + krb5_ctx = talloc_zero(kctx->rctx, struct krb5_ctx); + if (krb5_ctx == NULL) { + ret = ENOMEM; + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error allocating krb5_ctx\n"); + goto done; + } + + /* Set default Kerberos options */ + krb5_ctx->opts = talloc_zero_array(krb5_ctx, struct dp_option, KRB5_OPTS); + if (krb5_ctx->opts == NULL) { + ret = ENOMEM; + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error allocating krb5_ctx opts\n"); + goto done; + } + + for (i = 0; i < KRB5_OPTS; i++) { + krb5_ctx->opts[i].opt_name = default_krb5_opts[i].opt_name; + krb5_ctx->opts[i].type = default_krb5_opts[i].type; + krb5_ctx->opts[i].def_val = default_krb5_opts[i].def_val; + switch (krb5_ctx->opts[i].type) { + case DP_OPT_STRING: + ret = dp_opt_set_string(krb5_ctx->opts, i, + default_krb5_opts[i].def_val.string); + break; + case DP_OPT_BLOB: + ret = dp_opt_set_blob(krb5_ctx->opts, i, + default_krb5_opts[i].def_val.blob); + break; + case DP_OPT_NUMBER: + ret = dp_opt_set_int(krb5_ctx->opts, i, + default_krb5_opts[i].def_val.number); + break; + case DP_OPT_BOOL: + ret = dp_opt_set_bool(krb5_ctx->opts, i, + default_krb5_opts[i].def_val.boolean); + break; + default: + ret = EINVAL; + } + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed setting default renewal kerberos " + "options [%d]: %s\n", ret, sss_strerror(ret)); + talloc_free(krb5_ctx->opts); + goto done; + } + } + + ret = confdb_get_bool(kctx->rctx->cdb, + kctx->rctx->confdb_service_path, + CONFDB_KCM_TGT_RENEWAL, false, + &tgt_renewal); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to retrieve TGT Renewal confdb value " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Option [%s] set to [%s]\n", + CONFDB_KCM_TGT_RENEWAL, + tgt_renewal ? "true" : "false"); + if (tgt_renewal == false) { + ret = ENOTSUP; + goto done; + } + + ret = confdb_get_string(kctx->rctx->cdb, + kctx->rctx, + kctx->rctx->confdb_service_path, + CONFDB_KCM_TGT_RENEWAL_INHERIT, + NULL, + &tgt_renewal_inherit); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to retrieve TGT Renewal inherit confdb " + "valule [%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Option [%s] set to [%s]\n", + CONFDB_KCM_TGT_RENEWAL_INHERIT, + tgt_renewal_inherit == NULL ? "none" : tgt_renewal_inherit); + + /* Override with config options */ + if (tgt_renewal_inherit == NULL) { + ret = kcm_read_options(kctx, kctx->rctx->cdb, kctx->rctx->confdb_service_path, + &lifetime, &rtime, &validate, &canonicalize, + &timeout, &renew_intv); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to read krb5 options from " + "[kcm] section [%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + } else { + conf_path = talloc_asprintf(kctx->rctx, CONFDB_DOMAIN_PATH_TMPL, + tgt_renewal_inherit); + if (conf_path == NULL) { + ret = ENOMEM; + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error allocating conf_path\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Inherit krb5 options for domain [%s] for renewals\n", + conf_path); + ret = kcm_read_options(kctx, kctx->rctx->cdb, conf_path, + &lifetime, &rtime, &validate, &canonicalize, + &timeout, &renew_intv); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed reading domain [%s] inherit krb5 options " + "[%d]: %s\n", conf_path, ret, sss_strerror(ret)); + goto done; + } + } + + ret = kcm_set_options(krb5_ctx, lifetime, rtime, validate, canonicalize, + timeout, renew_intv, &renew_intv_tm); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed setting krb5 options for renewal " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + *_renew_intv = renew_intv_tm; + *_krb5_ctx = krb5_ctx; + ret = EOK; +done: + if (ret != EOK) { + talloc_free(krb5_ctx); + } + return ret; +} + +static errno_t kcm_child_req_setup(TALLOC_CTX *mem_ctx, + struct kcm_auth_data *auth_data, + struct krb5_ctx *krb5_ctx, + struct krb5child_req **_req) +{ + struct krb5child_req *krreq; + errno_t ret; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Setup for renewal of [%s] " \ + "for principal name [%s]\n", + auth_data->upn, + auth_data->ccname); + + krreq = talloc_zero(mem_ctx, struct krb5child_req); + if (krreq == NULL) { + ret = ENOMEM; + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to alloc krreq [%d]: %s\n", + ret, strerror(ret)); + goto fail; + } + + krreq->krb5_ctx = krb5_ctx; + + /* Set uid and gid */ + krreq->uid = auth_data->uid; + krreq->gid = auth_data->gid; + + krreq->upn = talloc_strdup(krreq, auth_data->upn); + if (krreq->upn == NULL) { + ret = ENOMEM; + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to strdup krreq->upn [%d]: %s\n", + ret, strerror(ret)); + goto fail; + } + + krreq->ccname = talloc_asprintf(krreq, "KCM:%s", auth_data->ccname); + if (krreq->ccname == NULL) { + ret = ENOMEM; + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to strdup krreq->ccname [%d]: %s\n", + ret, strerror(ret)); + goto fail; + } + + /* Set PAM Data */ + krreq->pd = create_pam_data(krreq); + if (krreq->pd == NULL) { + ret = ENOMEM; + DEBUG(SSSDBG_FATAL_FAILURE, "Failed creating pam data on krreq->pd " + "[%d]: %s\n", ret, strerror(ret)); + goto fail; + } + + krreq->pd->cmd = SSS_CMD_RENEW; + krreq->pd->user = talloc_strdup(krreq->pd, auth_data->upn); + if (krreq->pd->user == NULL) { + ret = ENOMEM; + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to strdup krreq->pd->user " + "[%d]: %s\n", ret, strerror(ret)); + goto fail; + } + + /* Set authtok values */ + sss_authtok_set_empty(krreq->pd->newauthtok); + + ret = sss_authtok_set_ccfile(krreq->pd->authtok, krreq->ccname, 0); + if (ret != EOK) { + ret = ENOMEM; + DEBUG(SSSDBG_FATAL_FAILURE, "Failed setting authtok krreq->ccname" + "[%d]: %s\n", ret, strerror(ret)); + goto fail; + } + + krreq->old_ccname = krreq->ccname; + + *_req = krreq; + + return EOK; +fail: + talloc_zfree(krreq); + return ret; +} + +static void kcm_renew_tgt(struct tevent_context *ev, + struct tevent_immediate *imm, + void *private_data) +{ + struct kcm_auth_data *auth_data; + struct tevent_req *req; + struct kcm_renew_auth_ctx *ctx; + errno_t ret; + + auth_data = talloc_get_type(private_data, struct kcm_auth_data); + + ctx = talloc_zero(auth_data, struct kcm_renew_auth_ctx); + if (ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to allocate renew auth ctx\n"); + return; + } + auth_data->auth_ctx = ctx; + + ret = kcm_child_req_setup(ctx, auth_data, auth_data->krb5_ctx, &ctx->kr); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to setup krb5 child for renewal [%d]: %s\n", + ret, sss_strerror(ret)); + talloc_free(auth_data); + return; + } + + req = handle_child_send(ctx, ev, ctx->kr); + if (req == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to trigger krb5 child process request" + "[%d]: %s\n", ret, sss_strerror(ret)); + talloc_free(auth_data); + return; + } + + tevent_req_set_callback(req, kcm_renew_tgt_done, auth_data); + + return; +} + +static void kcm_renew_tgt_done(struct tevent_req *req) +{ + struct kcm_auth_data *auth_data; + struct kcm_renew_auth_ctx *ctx; + int ret; + struct krb5_child_response *res; + + auth_data = tevent_req_callback_data(req, struct kcm_auth_data); + ctx = auth_data->auth_ctx; + + ret = handle_child_recv(req, ctx, &ctx->buf, &ctx->len); + talloc_free(req); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to receive krb5 child process request" + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + ret = parse_krb5_child_response(ctx, ctx->buf, ctx->len, ctx->kr->pd, + 0, &res); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Krb5 child returned error! Please " \ + "inspect the krb5_child.log file. " + " Error [%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + if (res->msg_status != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "Renewal failed - krb5_child [%d]\n", + res->msg_status); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Successfully renewed [%s]\n", res->ccname); +done: + talloc_zfree(ctx); + talloc_zfree(auth_data); + return; +} + +static errno_t kcm_creds_check_times(TALLOC_CTX *mem_ctx, + struct kcm_renew_tgt_ctx *renew_tgt_ctx, + krb5_creds *creds, + struct kcm_ccache *cc, + const char *client_name) +{ + struct tgt_times tgtt; + time_t now; + time_t start_renew; + struct kcm_auth_data *auth_data; + struct tevent_immediate *imm; + int ret; + + memset(&tgtt, 0, sizeof(tgtt)); + tgtt.authtime = creds->times.authtime; + tgtt.starttime = creds->times.starttime; + tgtt.endtime = creds->times.endtime; + tgtt.renew_till = creds->times.renew_till; + + now = time(NULL); + /* Attempt renewal only after half of the ticket lifetime has exceeded */ + start_renew = (time_t) (tgtt.starttime + 0.5 * (tgtt.endtime - tgtt.starttime)); + if (tgtt.renew_till >= tgtt.endtime && tgtt.renew_till >= now + && tgtt.endtime >= now && start_renew <= now) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Renewal cred ready!\n"); + auth_data = talloc_zero(renew_tgt_ctx, struct kcm_auth_data); + if (auth_data == NULL) { + ret = ENOMEM; + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to allocate auth_data for renewals\n"); + goto done; + } + + auth_data->krb5_ctx = renew_tgt_ctx->krb5_ctx; + auth_data->upn = talloc_strdup(auth_data, client_name); + auth_data->uid = cc->owner.uid; + auth_data->gid = cc->owner.gid; + auth_data->ccname = cc->name; + if (auth_data->upn == NULL) { + ret = ENOMEM; + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to allocate auth_data->upn for renewals\n"); + goto done; + } + + imm = tevent_create_immediate(auth_data); + if (imm == NULL) { + ret = ENOMEM; + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_create_immediate failed\n"); + goto done; + } + + tevent_schedule_immediate(imm, renew_tgt_ctx->ev, kcm_renew_tgt, + auth_data); + } else { + DEBUG(SSSDBG_TRACE_INTERNAL, "Time not applicable\n"); + } + + ret = EOK; +done: + return ret; +} + +errno_t kcm_renew_all_tgts(TALLOC_CTX *mem_ctx, + struct kcm_renew_tgt_ctx *renew_tgt_ctx, + struct kcm_ccache **cc_list) +{ + TALLOC_CTX *tmp_ctx; + size_t count = 0; + int ret; + struct kcm_ccache *cc; + char *client_name; + krb5_context krb_context; + krb5_creds **extracted_creds; + krb5_error_code kerr; + + if (cc_list == NULL) { + return EOK; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to create tmp talloc_ctx\n"); + return ENOMEM; + } + + kerr = krb5_init_context(&krb_context); + if (kerr != 0) { + ret = ENOMEM; + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to init krb5 context\n"); + goto done; + } + + count = talloc_array_length(cc_list); + if (count <= 1) { + DEBUG(SSSDBG_TRACE_FUNC, "No renewal entries found.\n"); + ret = EOK; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Found [%zu] renewal entries.\n", count - 1); + for (int i = 0; i < count - 1; i++) { + cc = cc_list[i]; + DEBUG(SSSDBG_TRACE_FUNC, + "Checking ccache [%s] for creds to renew\n", cc->name); + + extracted_creds = kcm_cc_unmarshal(tmp_ctx, krb_context, cc); + if (extracted_creds == NULL) { + ret = ENOMEM; + DEBUG(SSSDBG_CRIT_FAILURE, "Failed unmarshaling creds\n"); + goto done; + } + + for (int j = 0; extracted_creds[j] != NULL; j++) { + kerr = krb5_unparse_name(tmp_ctx, extracted_creds[j]->client, + &client_name); + if (kerr != 0) { + ret = EIO; + DEBUG(SSSDBG_CRIT_FAILURE, "Failed unparsing name\n"); + goto done; + } + + kcm_creds_check_times(tmp_ctx, renew_tgt_ctx, extracted_creds[j], + cc, client_name); + } + } + + ret = EOK; +done: + if (tmp_ctx != NULL) { + talloc_free(tmp_ctx); + } + krb5_free_context(krb_context); + return ret; +} + +static void kcm_renew_tgt_timer_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *data) +{ + struct kcm_renew_tgt_ctx *renew_tgt_ctx; + errno_t ret; + struct timeval next; + struct kcm_ccache **cc_list; + TALLOC_CTX *tmp_ctx; + + renew_tgt_ctx = talloc_get_type(data, struct kcm_renew_tgt_ctx); + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failure in tmp_ctx talloc_new\n"); + return; + } + + /* forget the timer event, it will be freed by the tevent timer loop */ + renew_tgt_ctx->te = NULL; + + /* Prepare KCM ccache list for renewals */ + ret = kcm_ccdb_renew_tgts(tmp_ctx, renew_tgt_ctx->krb5_ctx, + ev, renew_tgt_ctx->db, &cc_list); + if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_ALL, "No ccache renewal entries to prepare.\n"); + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to retrieve list of TGTs for renewal " + "preparation [%d]: %s\n", ret, sss_strerror(ret)); + } + + if (ret == EOK) { + ret = kcm_renew_all_tgts(tmp_ctx, renew_tgt_ctx, cc_list); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to successfully execute renewal of TGT list" + "[%d]: %s\n", ret, sss_strerror(ret)); + } + } + + /* Reschedule timer */ + next = sss_tevent_timeval_current_ofs_time_t(renew_tgt_ctx->timer_interval); + renew_tgt_ctx->te = tevent_add_timer(ev, renew_tgt_ctx, + next, kcm_renew_tgt_timer_handler, + renew_tgt_ctx); + if (renew_tgt_ctx->te == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to setup timer, renewals will be " + "disabled until the next interval triggers\n"); + talloc_zfree(renew_tgt_ctx); + } + + talloc_free(tmp_ctx); + return; +} + +errno_t kcm_renewal_setup(struct resp_ctx *rctx, + struct krb5_ctx *krb5_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + time_t renew_intv) +{ + int ret; + struct timeval next; + + krb5_ctx->kcm_renew_tgt_ctx = talloc_zero(krb5_ctx, struct kcm_renew_tgt_ctx); + if (krb5_ctx->kcm_renew_tgt_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n"); + return ENOMEM; + } + + krb5_ctx->kcm_renew_tgt_ctx->rctx = rctx; + krb5_ctx->kcm_renew_tgt_ctx->krb5_ctx = krb5_ctx; + krb5_ctx->kcm_renew_tgt_ctx->db = db, + krb5_ctx->kcm_renew_tgt_ctx->ev = ev; + krb5_ctx->kcm_renew_tgt_ctx->timer_interval = renew_intv; + + /* Check KCM for tickets to renew */ + next = sss_tevent_timeval_current_ofs_time_t( + krb5_ctx->kcm_renew_tgt_ctx->timer_interval); + krb5_ctx->kcm_renew_tgt_ctx->te = tevent_add_timer(ev, krb5_ctx->kcm_renew_tgt_ctx, + next, + kcm_renew_tgt_timer_handler, + krb5_ctx->kcm_renew_tgt_ctx); + if (krb5_ctx->kcm_renew_tgt_ctx->te == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to setup renewal timer\n"); + ret = ENOMEM; + goto fail; + } + + return EOK; + +fail: + talloc_zfree(krb5_ctx->renew_tgt_ctx); + return ret; +} diff --git a/src/responder/kcm/kcm_renew.h b/src/responder/kcm/kcm_renew.h new file mode 100644 index 0000000..0d69f42 --- /dev/null +++ b/src/responder/kcm/kcm_renew.h @@ -0,0 +1,53 @@ +/* + SSSD + + KCM Renewal, private header file + + Authors: + Justin Stephenson <jstephen@redhat.com> + + Copyright (C) 2020 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/>. +*/ + +#ifndef __KCM_RENEW_H__ +#define __KCM_RENEW_H__ + +#include "providers/krb5/krb5_common.h" +#include "src/providers/krb5/krb5_ccache.h" +#include "responder/kcm/kcmsrv_pvt.h" +#include "util/sss_ptr_hash.h" + +struct kcm_renew_tgt_ctx { + struct kcm_ccache **cc_list; + struct tevent_context *ev; + struct krb5_ctx *krb5_ctx; + struct resp_ctx *rctx; + struct kcm_ccdb *db; + time_t timer_interval; + struct tevent_timer *te; +}; + + +int kcm_get_renewal_config(struct kcm_ctx *kctx, + struct krb5_ctx **_krb5_ctx, + time_t *_renew_intv); + +errno_t kcm_renewal_setup(struct resp_ctx *rctx, struct krb5_ctx *kctx, + struct tevent_context *ev, struct kcm_ccdb *db, + time_t renew_intv); + +#endif /* __KCM_RENEW_H__ */ diff --git a/src/responder/kcm/kcmsrv_ccache.c b/src/responder/kcm/kcmsrv_ccache.c new file mode 100644 index 0000000..6e4ea64 --- /dev/null +++ b/src/responder/kcm/kcmsrv_ccache.c @@ -0,0 +1,1724 @@ +/* + SSSD + + KCM Server - the KCM ccache operations + + Copyright (C) Red Hat, 2016 + + 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 "config.h" + +#include "util/crypto/sss_crypto.h" +#include "util/util.h" +#include "util/sss_krb5.h" +#include "src/providers/krb5/krb5_ccache.h" +#include "responder/kcm/kcm_renew.h" +#include "responder/kcm/kcmsrv_ccache.h" +#include "responder/kcm/kcmsrv_ccache_pvt.h" +#include "responder/kcm/kcmsrv_ccache_be.h" + + +static struct kcm_cred *kcm_cred_dup(TALLOC_CTX *mem_ctx, + struct kcm_cred *crd); + +static int kcm_cc_destructor(struct kcm_ccache *cc) +{ + if (cc == NULL) { + return 0; + } + + if (cc->client != NULL) { + krb5_free_principal(NULL, cc->client); + } + return 0; +} + +errno_t kcm_cc_new(TALLOC_CTX *mem_ctx, + krb5_context k5c, + struct cli_creds *owner, + const char *name, + krb5_principal princ, + struct kcm_ccache **_cc) +{ + struct kcm_ccache *cc = NULL; + krb5_error_code kret; + errno_t ret; + + cc = talloc_zero(mem_ctx, struct kcm_ccache); + if (cc == NULL) { + return ENOMEM; + } + + ret = kcm_check_name(name, owner); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Name %s is malformed\n", name); + goto done; + } + + cc->name = talloc_strdup(cc, name); + if (cc->name == NULL) { + ret = ENOMEM; + goto done; + } + + uuid_generate(cc->uuid); + + if (princ) { + kret = krb5_copy_principal(k5c, princ, &cc->client); + if (kret != 0) { + const char *err_msg = sss_krb5_get_error_message(k5c, kret); + DEBUG(SSSDBG_OP_FAILURE, + "krb5_copy_principal failed: [%d][%s]\n", kret, err_msg); + sss_krb5_free_error_message(k5c, err_msg); + ret = ERR_INTERNAL; + goto done; + } + } + + cc->owner.uid = cli_creds_get_uid(owner); + cc->owner.gid = cli_creds_get_gid(owner); + cc->kdc_offset = 0; + + talloc_set_destructor(cc, kcm_cc_destructor); + *_cc = cc; + ret = EOK; +done: + if (ret != EOK) { + talloc_free(cc); + } + return ret; +} + +struct kcm_ccache *kcm_cc_dup(TALLOC_CTX *mem_ctx, + const struct kcm_ccache *cc) +{ + struct kcm_ccache *dup; + struct kcm_cred *crd_dup; + struct kcm_cred *crd; + + dup = talloc_zero(mem_ctx, struct kcm_ccache); + if (dup == NULL) { + return NULL; + } + memcpy(dup, cc, sizeof(struct kcm_ccache)); + + dup->creds = NULL; + DLIST_FOR_EACH(crd, cc->creds) { + crd_dup = kcm_cred_dup(dup, crd); + if (crd_dup == NULL) { + talloc_free(dup); + return NULL; + } + + DLIST_ADD(dup->creds, crd_dup); + } + + return dup; +} + +const char *kcm_cc_get_name(struct kcm_ccache *cc) +{ + return cc ? cc->name : NULL; +} + +errno_t kcm_cc_get_uuid(struct kcm_ccache *cc, uuid_t _uuid) +{ + if (cc == NULL) { + return EINVAL; + } + uuid_copy(_uuid, cc->uuid); + return EOK; +} + +krb5_principal kcm_cc_get_client_principal(struct kcm_ccache *cc) +{ + return cc ? cc->client : NULL; +} + +bool kcm_cc_access(struct kcm_ccache *cc, + struct cli_creds *client) +{ + bool ok; + uid_t uid = cli_creds_get_uid(client); + gid_t gid = cli_creds_get_gid(client); + + if (cc == NULL) { + return false; + } + + if (uid == 0 && gid == 0) { + /* root can access any ccache */ + return true; + } + + ok = ((cc->owner.uid == uid) && (cc->owner.gid == gid)); + if (!ok) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Client %"SPRIuid":%"SPRIgid" has no access to ccache %s\n", + cli_creds_get_uid(client), + cli_creds_get_gid(client), + cc->name); + } + return ok; +} + +int32_t kcm_cc_get_offset(struct kcm_ccache *cc) +{ + return cc ? cc->kdc_offset : INT32_MAX; +} + +errno_t kcm_cc_store_cred_blob(struct kcm_ccache *cc, + struct sss_iobuf *cred_blob) +{ + struct kcm_cred *kcreds; + uuid_t uuid; + errno_t ret; + + if (cc == NULL || cred_blob == NULL) { + return EINVAL; + } + + uuid_generate(uuid); + kcreds = kcm_cred_new(cc, uuid, cred_blob); + if (kcreds == NULL) { + return ENOMEM; + } + + ret = kcm_cc_store_creds(cc, kcreds); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +struct kcm_cred *kcm_cc_get_cred(struct kcm_ccache *cc) +{ + if (cc == NULL) { + return NULL; + } + + return cc->creds; +} + +struct kcm_cred *kcm_cc_next_cred(struct kcm_cred *crd) +{ + if (crd == NULL) { + return NULL; + } + + return crd->next; +} + +struct kcm_cred *kcm_cred_new(TALLOC_CTX *mem_ctx, + uuid_t uuid, + struct sss_iobuf *cred_blob) +{ + struct kcm_cred *kcreds; + + kcreds = talloc_zero(mem_ctx, struct kcm_cred); + if (kcreds == NULL) { + return NULL; + } + uuid_copy(kcreds->uuid, uuid); + kcreds->cred_blob = talloc_steal(kcreds, cred_blob); + return kcreds; +} + +static struct kcm_cred *kcm_cred_dup(TALLOC_CTX *mem_ctx, + struct kcm_cred *crd) +{ + struct kcm_cred *dup; + + dup = talloc_zero(mem_ctx, struct kcm_cred); + if (dup == NULL) { + return NULL; + } + + uuid_copy(dup->uuid, crd->uuid); + dup->cred_blob = crd->cred_blob; + + return dup; +} + +#ifdef HAVE_KRB5_UNMARSHAL_CREDENTIALS +static krb5_creds *kcm_cred_to_krb5(krb5_context kctx, struct kcm_cred *kcm_crd) +{ + krb5_error_code kerr; + krb5_creds *kcrd; + krb5_data data; + + get_krb5_data_from_cred(kcm_crd->cred_blob, &data); + + kerr = krb5_unmarshal_credentials(kctx, &data, &kcrd); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to unmarshal credentials\n"); + return NULL; + } + + return kcrd; +} +#endif + +static errno_t +kcm_cc_remove_duplicates(struct kcm_ccache *cc, + struct kcm_cred *kcm_crd) +{ +#ifdef HAVE_KRB5_UNMARSHAL_CREDENTIALS + struct kcm_cred *p, *q; + krb5_error_code kerr; + krb5_context kctx; + krb5_creds *kcrd_cc; + krb5_creds *kcrd; + errno_t ret; + bool bret; + + kerr = krb5_init_context(&kctx); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to init krb5 context\n"); + return EIO; + } + + kcrd = kcm_cred_to_krb5(kctx, kcm_crd); + if (kcrd == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to convert kcm cred to krb5\n"); + ret = ERR_INTERNAL; + goto done; + } + + DLIST_FOR_EACH_SAFE(p, q, cc->creds) { + kcrd_cc = kcm_cred_to_krb5(kctx, p); + if (kcrd_cc == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to convert kcm cred to krb5\n"); + ret = ERR_INTERNAL; + goto done; + } + + bret = sss_krb5_creds_compare(kctx, kcrd, kcrd_cc); + krb5_free_creds(kctx, kcrd_cc); + if (!bret) { + continue; + } + + /* This cred is the same ticket. We will replace it with the new one. */ + DLIST_REMOVE(cc->creds, p); + } + + ret = EOK; + +done: + krb5_free_creds(kctx, kcrd); + krb5_free_context(kctx); + + return ret; +#else + return EOK; +#endif +} + +/* Add a cred to ccache */ +errno_t kcm_cc_store_creds(struct kcm_ccache *cc, + struct kcm_cred *crd) +{ + errno_t ret; + + ret = kcm_cc_remove_duplicates(cc, crd); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to remove duplicate credentials " + "[%d]: %s\n", ret, sss_strerror(ret)); + } + + DLIST_ADD(cc->creds, crd); + talloc_steal(cc, crd); + return EOK; +} + +errno_t kcm_cc_set_header(struct kcm_ccache *cc, + const char *sec_key, + struct cli_creds *client) +{ + errno_t ret; + + ret = sec_key_parse(cc, sec_key, &cc->name, cc->uuid); + if (ret != EOK) { + return ret; + } + + /* We rely on sssd-secrets only searching the user's subtree so we + * set the ownership to the client + */ + cc->owner.uid = cli_creds_get_uid(client); + cc->owner.gid = cli_creds_get_gid(client); + + return EOK; +} + +#ifdef HAVE_KCM_RENEWAL +static int kcm_cc_unmarshal_destructor(krb5_creds **creds) +{ + krb5_error_code kerr; + krb5_context krb_ctx; + int i; + + kerr = krb5_init_context(&krb_ctx); + if (kerr != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to init krb5 context\n"); + return 1; + } + + for (i = 0; creds[i] != NULL; i++) { + krb5_free_creds(krb_ctx, creds[i]); + } + + krb5_free_context(krb_ctx); + + return 0; +} +#endif + +krb5_creds **kcm_cc_unmarshal(TALLOC_CTX *mem_ctx, + krb5_context krb_context, + struct kcm_ccache *cc) +{ +#ifndef HAVE_KCM_RENEWAL + return NULL; +#else + TALLOC_CTX *tmp_ctx; + struct kcm_cred *cred; + krb5_creds **cred_list; + int i = 0; + int count = 0; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + goto done; + } + + for (cred = kcm_cc_get_cred(cc); cred != NULL; cred = kcm_cc_next_cred(cred)) { + count++; + } + + cred_list = talloc_array(tmp_ctx, krb5_creds *, count + 1); + + for (cred = kcm_cc_get_cred(cc); cred != NULL; cred = kcm_cc_next_cred(cred), i++) { + cred_list[i] = kcm_cred_to_krb5(krb_context, cred); + if (cred_list[i] == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to convert kcm cred to krb5\n"); + goto done; + } + } + + cred_list[count] = NULL; + talloc_set_destructor(cred_list, kcm_cc_unmarshal_destructor); + + talloc_steal(mem_ctx, cred_list); + + return cred_list; +done: + talloc_free(tmp_ctx); + return NULL; +#endif +} + +errno_t kcm_cred_get_uuid(struct kcm_cred *crd, uuid_t _uuid) +{ + if (crd == NULL) { + return EINVAL; + } + uuid_copy(_uuid, crd->uuid); + return EOK; +} + +struct sss_iobuf *kcm_cred_get_creds(struct kcm_cred *crd) +{ + return crd ? crd->cred_blob : NULL; +} + +errno_t kcm_ccdb_renew_tgts(TALLOC_CTX *mem_ctx, + struct krb5_ctx *krb5_ctx, + struct tevent_context *ev, + struct kcm_ccdb *ccdb, + struct kcm_ccache ***_cc_list) +{ + struct kcm_ccache **cc; + errno_t ret; + + if (krb5_ctx == NULL || ev == NULL || ccdb == NULL) { + ret = EINVAL; + return ret; + } + + if (ccdb->ops->list_all_cc == NULL) { + ret = EINVAL; + DEBUG(SSSDBG_TRACE_INTERNAL, "List all cc function not available\n"); + goto done; + } + + ret = ccdb->ops->list_all_cc(mem_ctx, krb5_ctx, ev, ccdb, &cc); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failure to retrieve list of ccaches" + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } else if (ret == ENOENT) { + goto done; + } + + *_cc_list = talloc_steal(mem_ctx, cc); + + ret = EOK; +done: + + return ret; +} + +struct kcm_ccdb *kcm_ccdb_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct confdb_ctx *cdb, + const char *confdb_service_path, + enum kcm_ccdb_be cc_be) +{ + errno_t ret; + struct kcm_ccdb *ccdb = NULL; + + if (ev == NULL) { + return NULL; + } + + ccdb = talloc_zero(mem_ctx, struct kcm_ccdb); + if (ccdb == NULL) { + return NULL; + } + ccdb->ev = ev; + + switch (cc_be) { + case CCDB_BE_MEMORY: + DEBUG(SSSDBG_FUNC_DATA, "KCM back end: memory\n"); + ccdb->ops = &ccdb_mem_ops; + break; + case CCDB_BE_SECDB: + DEBUG(SSSDBG_FUNC_DATA, "KCM back end: libsss_secrets\n"); + ccdb->ops = &ccdb_secdb_ops; + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unknown ccache database backend\n"); + break; + } + + if (ccdb->ops == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Ccache database not initialized\n"); + talloc_free(ccdb); + return NULL; + } + + ret = ccdb->ops->init(ccdb, cdb, confdb_service_path); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot initialize ccache database\n"); + talloc_free(ccdb); + return NULL; + } + + return ccdb; +} + +struct kcm_ccdb_nextid_state { + char *next_cc; + struct kcm_ccdb *db; + struct cli_creds *client; +}; + +static void kcm_ccdb_nextid_done(struct tevent_req *subreq); + +struct tevent_req *kcm_ccdb_nextid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct kcm_ccdb_nextid_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct kcm_ccdb_nextid_state); + if (req == NULL) { + return NULL; + } + state->db = db; + state->client = client; + + if (ev == NULL || db == NULL || client == NULL) { + ret = EINVAL; + goto immediate; + } + + subreq = state->db->ops->nextid_send(state, ev, state->db, client); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_ccdb_nextid_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kcm_ccdb_nextid_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_ccdb_nextid_state *state = tevent_req_data(req, + struct kcm_ccdb_nextid_state); + errno_t ret; + unsigned int nextid; + + ret = state->db->ops->nextid_recv(subreq, &nextid); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to generate next UID [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + state->next_cc = talloc_asprintf(state, "%"SPRIuid":%u", + cli_creds_get_uid(state->client), + nextid); + if (state->next_cc == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed\n"); + tevent_req_error(req, ENOMEM); + return; + } + + DEBUG(SSSDBG_TRACE_LIBS, "generated %s\n", state->next_cc); + tevent_req_done(req); +} + +errno_t kcm_ccdb_nextid_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + char **_next_cc) +{ + struct kcm_ccdb_nextid_state *state = tevent_req_data(req, + struct kcm_ccdb_nextid_state); + TEVENT_REQ_RETURN_ON_ERROR(req); + *_next_cc = talloc_steal(mem_ctx, state->next_cc); + return EOK; +} + +struct kcm_ccdb_list_state { + struct kcm_ccdb *db; + struct cli_creds *client; + + uuid_t *uuid_list; +}; + +static void kcm_ccdb_list_done(struct tevent_req *subreq); + +struct tevent_req *kcm_ccdb_list_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct kcm_ccdb_list_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct kcm_ccdb_list_state); + if (req == NULL) { + return NULL; + } + state->db = db; + state->client = client; + + if (ev == NULL || db == NULL || client == NULL) { + ret = EINVAL; + goto immediate; + } + + subreq = state->db->ops->list_send(state, + ev, + state->db, + client); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_ccdb_list_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kcm_ccdb_list_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_ccdb_list_state *state = tevent_req_data(req, + struct kcm_ccdb_list_state); + errno_t ret; + + ret = state->db->ops->list_recv(subreq, state, &state->uuid_list); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to list all ccaches [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t kcm_ccdb_list_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uuid_t **_uuid_list) +{ + struct kcm_ccdb_list_state *state = tevent_req_data(req, + struct kcm_ccdb_list_state); + TEVENT_REQ_RETURN_ON_ERROR(req); + *_uuid_list = talloc_steal(mem_ctx, state->uuid_list); + return EOK; +} + +struct kcm_ccdb_get_default_state { + struct kcm_ccdb *db; + uuid_t uuid; +}; + +static void kcm_ccdb_get_default_done(struct tevent_req *subreq); + +struct tevent_req *kcm_ccdb_get_default_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct kcm_ccdb_get_default_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct kcm_ccdb_get_default_state); + if (req == NULL) { + return NULL; + } + state->db = db; + + if (ev == NULL || db == NULL || client == NULL) { + ret = EINVAL; + goto immediate; + } + + subreq = db->ops->get_default_send(state, ev, db, client); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_ccdb_get_default_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kcm_ccdb_get_default_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_ccdb_get_default_state *state = tevent_req_data(req, + struct kcm_ccdb_get_default_state); + errno_t ret; + + ret = state->db->ops->get_default_recv(subreq, state->uuid); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to get the default ccache [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t kcm_ccdb_get_default_recv(struct tevent_req *req, + uuid_t *uuid) +{ + struct kcm_ccdb_get_default_state *state = tevent_req_data(req, + struct kcm_ccdb_get_default_state); + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (uuid != NULL) { + /* The caller might supply a NULL dfl to just check if there is + * some default ccache + */ + uuid_copy(*uuid, state->uuid); + } + + return EOK; +} + +struct kcm_ccdb_set_default_state { + struct tevent_context *ev; + struct kcm_ccdb *db; + struct cli_creds *client; + uuid_t uuid; +}; + +static void kcm_ccdb_set_default_uuid_resolved(struct tevent_req *subreq); +static void kcm_ccdb_set_default_done(struct tevent_req *subreq); + +struct tevent_req *kcm_ccdb_set_default_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct kcm_ccdb_set_default_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct kcm_ccdb_set_default_state); + if (req == NULL) { + return NULL; + } + state->db = db; + state->ev = ev; + state->client = client; + uuid_copy(state->uuid, uuid); + + if (ev == NULL || db == NULL || client == NULL) { + ret = EINVAL; + goto immediate; + } + + if (uuid_is_null(uuid)) { + /* NULL UUID means to just reset the default to 'no default' */ + subreq = state->db->ops->set_default_send(state, + state->ev, + state->db, + state->client, + state->uuid); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_ccdb_set_default_done, req); + } else { + /* Otherwise we need to check if the client can access the UUID + * about to be set as default + */ + subreq = db->ops->getbyuuid_send(state, ev, db, client, uuid); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_ccdb_set_default_uuid_resolved, req); + } + + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kcm_ccdb_set_default_uuid_resolved(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_ccdb_set_default_state *state = tevent_req_data(req, + struct kcm_ccdb_set_default_state); + errno_t ret; + bool ok; + struct kcm_ccache *cc; + + ret = state->db->ops->getbyuuid_recv(subreq, state, &cc); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to get cache by UUID [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (cc == NULL) { + DEBUG(SSSDBG_TRACE_LIBS, "No cache found by UUID\n"); + tevent_req_error(req, ERR_KCM_CC_END); + return; + } + + ok = kcm_cc_access(cc, state->client); + if (!ok) { + tevent_req_error(req, EACCES); + return; + } + + subreq = state->db->ops->set_default_send(state, + state->ev, + state->db, + state->client, + state->uuid); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, kcm_ccdb_set_default_done, req); +} + +static void kcm_ccdb_set_default_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_ccdb_set_default_state *state = tevent_req_data(req, + struct kcm_ccdb_set_default_state); + errno_t ret; + + ret = state->db->ops->set_default_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to set the default ccache [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t kcm_ccdb_set_default_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +struct kcm_ccdb_getbyname_state { + struct kcm_ccdb *db; + struct cli_creds *client; + + struct kcm_ccache *cc; +}; + +static void kcm_ccdb_getbyname_done(struct tevent_req *subreq); + +struct tevent_req *kcm_ccdb_getbyname_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + const char *name) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct kcm_ccdb_getbyname_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct kcm_ccdb_getbyname_state); + if (req == NULL) { + return NULL; + } + state->db = db; + state->client = client; + + if (ev == NULL || db == NULL || client == NULL || name == NULL) { + ret = EINVAL; + goto immediate; + } + + subreq = db->ops->getbyname_send(state, ev, db, client, name); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_ccdb_getbyname_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kcm_ccdb_getbyname_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_ccdb_getbyname_state *state = tevent_req_data(req, + struct kcm_ccdb_getbyname_state); + errno_t ret; + bool ok; + + ret = state->db->ops->getbyname_recv(subreq, state, &state->cc); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to get cache by name [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (state->cc == NULL) { + DEBUG(SSSDBG_TRACE_LIBS, "No cache found by name\n"); + tevent_req_done(req); + return; + } + + ok = kcm_cc_access(state->cc, state->client); + if (!ok) { + tevent_req_error(req, EACCES); + return; + } + + tevent_req_done(req); +} + +errno_t kcm_ccdb_getbyname_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct kcm_ccache **_cc) +{ + struct kcm_ccdb_getbyname_state *state = tevent_req_data(req, + struct kcm_ccdb_getbyname_state); + TEVENT_REQ_RETURN_ON_ERROR(req); + *_cc = talloc_steal(mem_ctx, state->cc); + return EOK; +} + +struct kcm_ccdb_getbyuuid_state { + struct kcm_ccdb *db; + struct cli_creds *client; + + struct kcm_ccache *cc; +}; + +static void kcm_ccdb_getbyuuid_done(struct tevent_req *subreq); + +struct tevent_req *kcm_ccdb_getbyuuid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct kcm_ccdb_getbyuuid_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct kcm_ccdb_getbyuuid_state); + if (req == NULL) { + return NULL; + } + state->db = db; + state->client = client; + + if (ev == NULL || db == NULL || client == NULL) { + ret = EINVAL; + goto immediate; + } + + subreq = db->ops->getbyuuid_send(state, ev, db, client, uuid); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_ccdb_getbyuuid_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kcm_ccdb_getbyuuid_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_ccdb_getbyuuid_state *state = tevent_req_data(req, + struct kcm_ccdb_getbyuuid_state); + errno_t ret; + bool ok; + + ret = state->db->ops->getbyuuid_recv(subreq, state, &state->cc); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to get cache by UUID [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (state->cc == NULL) { + DEBUG(SSSDBG_TRACE_LIBS, "No cache found by UUID\n"); + tevent_req_done(req); + return; + } + + ok = kcm_cc_access(state->cc, state->client); + if (!ok) { + tevent_req_error(req, EACCES); + return; + } + + tevent_req_done(req); +} + +errno_t kcm_ccdb_getbyuuid_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct kcm_ccache **_cc) +{ + struct kcm_ccdb_getbyuuid_state *state = tevent_req_data(req, + struct kcm_ccdb_getbyuuid_state); + TEVENT_REQ_RETURN_ON_ERROR(req); + *_cc = talloc_steal(mem_ctx, state->cc); + return EOK; +} + +struct kcm_ccdb_name_by_uuid_state { + struct kcm_ccdb *db; + struct cli_creds *client; + + const char *name; +}; + +static void kcm_ccdb_name_by_uuid_done(struct tevent_req *subreq); + +struct tevent_req *kcm_ccdb_name_by_uuid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct kcm_ccdb_name_by_uuid_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, + &state, + struct kcm_ccdb_name_by_uuid_state); + if (req == NULL) { + return NULL; + } + state->db = db; + state->client = client; + + if (ev == NULL || db == NULL || client == NULL || uuid_is_null(uuid)) { + ret = EINVAL; + goto immediate; + } + + subreq = db->ops->name_by_uuid_send(state, ev, db, client, uuid); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_ccdb_name_by_uuid_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kcm_ccdb_name_by_uuid_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_ccdb_name_by_uuid_state *state = tevent_req_data(req, + struct kcm_ccdb_name_by_uuid_state); + errno_t ret; + + ret = state->db->ops->name_by_uuid_recv(subreq, state, &state->name); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to resolve cache by UUID [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t kcm_ccdb_name_by_uuid_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + const char **_name) +{ + struct kcm_ccdb_name_by_uuid_state *state = tevent_req_data(req, + struct kcm_ccdb_name_by_uuid_state); + TEVENT_REQ_RETURN_ON_ERROR(req); + *_name = talloc_steal(mem_ctx, state->name); + return EOK; +} + +struct kcm_ccdb_uuid_by_name_state { + struct kcm_ccdb *db; + struct cli_creds *client; + + uuid_t uuid; +}; + +static void kcm_ccdb_uuid_by_name_done(struct tevent_req *subreq); + +struct tevent_req *kcm_ccdb_uuid_by_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + const char *name) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct kcm_ccdb_uuid_by_name_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, + &state, + struct kcm_ccdb_uuid_by_name_state); + if (req == NULL) { + return NULL; + } + state->db = db; + state->client = client; + + if (ev == NULL || db == NULL || client == NULL || name == NULL) { + ret = EINVAL; + goto immediate; + } + + subreq = db->ops->uuid_by_name_send(state, ev, db, client, name); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_ccdb_uuid_by_name_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kcm_ccdb_uuid_by_name_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_ccdb_uuid_by_name_state *state = tevent_req_data(req, + struct kcm_ccdb_uuid_by_name_state); + errno_t ret; + + ret = state->db->ops->uuid_by_name_recv(subreq, state, state->uuid); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to resolve cache by UUID [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t kcm_ccdb_uuid_by_name_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uuid_t _uuid) +{ + struct kcm_ccdb_uuid_by_name_state *state = tevent_req_data(req, + struct kcm_ccdb_uuid_by_name_state); + TEVENT_REQ_RETURN_ON_ERROR(req); + uuid_copy(_uuid, state->uuid); + return EOK; +} + +struct kcm_ccdb_create_cc_state { + struct kcm_ccdb *db; +}; + +static void kcm_ccdb_create_done(struct tevent_req *subreq); + +struct tevent_req *kcm_ccdb_create_cc_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + struct kcm_ccache *cc) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct kcm_ccdb_create_cc_state *state = NULL; + errno_t ret; + bool ok; + + req = tevent_req_create(mem_ctx, &state, struct kcm_ccdb_create_cc_state); + if (req == NULL) { + return NULL; + } + state->db = db; + + if (ev == NULL || db == NULL || client == NULL || cc == NULL) { + ret = EINVAL; + goto immediate; + } + + ok = kcm_cc_access(cc, client); + if (!ok) { + ret = EACCES; + goto immediate; + } + + subreq = state->db->ops->create_send(state, + ev, + state->db, + client, + cc); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_ccdb_create_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kcm_ccdb_create_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_ccdb_create_cc_state *state = tevent_req_data(req, + struct kcm_ccdb_create_cc_state); + errno_t ret; + + ret = state->db->ops->create_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to create ccache [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t kcm_ccdb_create_cc_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +static void kcm_mod_ctx_clear(struct kcm_mod_ctx *mod_ctx) +{ + if (mod_ctx == NULL) { + return; + } + + mod_ctx->kdc_offset = INT32_MAX; + if (mod_ctx->client != NULL) { + krb5_free_principal(NULL, mod_ctx->client); + mod_ctx->client = NULL; + } + + return; +} + +struct kcm_mod_ctx *kcm_mod_ctx_new(TALLOC_CTX *mem_ctx) +{ + struct kcm_mod_ctx *mod_ctx; + + mod_ctx = talloc_zero(mem_ctx, struct kcm_mod_ctx); + if (mod_ctx == NULL) { + return NULL; + } + + kcm_mod_ctx_clear(mod_ctx); + return mod_ctx; +} + +errno_t kcm_mod_cc(struct kcm_ccache *cc, struct kcm_mod_ctx *mod_ctx) +{ + if (cc == NULL || mod_ctx == NULL) { + return EINVAL; + } + + if (mod_ctx->kdc_offset != INT32_MAX) { + cc->kdc_offset = mod_ctx->kdc_offset; + } + + if (mod_ctx->client != NULL) { + krb5_error_code kret; + + kret = krb5_copy_principal(NULL, mod_ctx->client, &cc->client); + if (kret != 0) { + DEBUG(SSSDBG_OP_FAILURE, + "krb5_copy_principal failed: %d\n", kret); + return ERR_INTERNAL; + } + } + + return EOK; +} + +struct kcm_ccdb_mod_cc_state { + struct kcm_ccdb *db; +}; + +static void kcm_ccdb_mod_done(struct tevent_req *subreq); + +struct tevent_req *kcm_ccdb_mod_cc_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid, + struct kcm_mod_ctx *mod_cc) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct kcm_ccdb_mod_cc_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct kcm_ccdb_mod_cc_state); + if (req == NULL) { + return NULL; + } + state->db = db; + + if (ev == NULL || db == NULL || client == NULL || mod_cc == NULL) { + ret = EINVAL; + goto immediate; + } + + subreq = state->db->ops->mod_send(state, + ev, + state->db, + client, + uuid, + mod_cc); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_ccdb_mod_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kcm_ccdb_mod_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_ccdb_mod_cc_state *state = tevent_req_data(req, + struct kcm_ccdb_mod_cc_state); + errno_t ret; + + ret = state->db->ops->mod_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to create ccache [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t kcm_ccdb_mod_cc_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +struct kcm_ccdb_store_cred_blob_state { + struct kcm_ccdb *db; +}; + +static void kcm_ccdb_store_cred_blob_done(struct tevent_req *subreq); + +struct tevent_req *kcm_ccdb_store_cred_blob_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid, + struct sss_iobuf *cred_blob) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct kcm_ccdb_store_cred_blob_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct kcm_ccdb_store_cred_blob_state); + if (req == NULL) { + return NULL; + } + state->db = db; + + if (ev == NULL || db == NULL || client == NULL || cred_blob == NULL) { + ret = EINVAL; + goto immediate; + } + + subreq = state->db->ops->store_cred_send(state, + ev, + state->db, + client, + uuid, + cred_blob); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_ccdb_store_cred_blob_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kcm_ccdb_store_cred_blob_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_ccdb_store_cred_blob_state *state = tevent_req_data(req, + struct kcm_ccdb_store_cred_blob_state); + errno_t ret; + + ret = state->db->ops->store_cred_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to create ccache [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t kcm_ccdb_store_cred_blob_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +struct kcm_ccdb_delete_cc_state { + struct tevent_context *ev; + struct kcm_ccdb *db; + struct cli_creds *client; + uuid_t uuid; +}; + +static void kcm_ccdb_delete_done(struct tevent_req *subreq); +static void kcm_ccdb_delete_get_default_done(struct tevent_req *subreq); +static void kcm_ccdb_delete_default_reset_done(struct tevent_req *subreq); + +struct tevent_req *kcm_ccdb_delete_cc_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct kcm_ccdb_delete_cc_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct kcm_ccdb_delete_cc_state); + if (req == NULL) { + return NULL; + } + state->db = db; + state->ev = ev; + state->client = client; + uuid_copy(state->uuid, uuid); + + if (ev == NULL || db == NULL || client == NULL) { + ret = EINVAL; + goto immediate; + } + + subreq = state->db->ops->delete_send(state, + state->ev, + state->db, + state->client, + state->uuid); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_ccdb_delete_done, req); + + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kcm_ccdb_delete_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_ccdb_delete_cc_state *state = tevent_req_data(req, + struct kcm_ccdb_delete_cc_state); + errno_t ret; + + ret = state->db->ops->delete_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to delete ccache [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + /* The delete operation must also check if the deleted ccache was + * the default and reset the default if it was + */ + subreq = state->db->ops->get_default_send(state, + state->ev, + state->db, + state->client); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, kcm_ccdb_delete_get_default_done, req); +} + +static void kcm_ccdb_delete_get_default_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_ccdb_delete_cc_state *state = tevent_req_data(req, + struct kcm_ccdb_delete_cc_state); + errno_t ret; + uuid_t dfl_uuid; + uuid_t null_uuid; + + ret = state->db->ops->get_default_recv(subreq, dfl_uuid); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to get the default ccache [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (uuid_compare(dfl_uuid, state->uuid) != 0) { + /* The ccache about to be deleted was not the default, quit */ + tevent_req_done(req); + return; + } + + /* If we deleted the default ccache, reset the default ccache to 'none' */ + uuid_clear(null_uuid); + + subreq = state->db->ops->set_default_send(state, + state->ev, + state->db, + state->client, + null_uuid); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, kcm_ccdb_delete_default_reset_done, req); +} + +static void kcm_ccdb_delete_default_reset_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_ccdb_delete_cc_state *state = tevent_req_data(req, + struct kcm_ccdb_delete_cc_state); + errno_t ret; + + ret = state->db->ops->set_default_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to NULL the default ccache [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t kcm_ccdb_delete_cc_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +void kcm_debug_uuid(uuid_t uuid) +{ + char dbgbuf[UUID_STR_SIZE]; + + if (!(debug_level & SSSDBG_TRACE_ALL) || uuid == NULL) { + return; + } + + uuid_unparse(uuid, dbgbuf); + DEBUG(SSSDBG_TRACE_ALL, "UUID: %s\n", dbgbuf); +} + +errno_t kcm_check_name(const char *name, struct cli_creds *client) +{ + char prefix[64]; + size_t prefix_len; + + prefix_len = snprintf(prefix, sizeof(prefix), + "%"SPRIuid, cli_creds_get_uid(client)); + + if (strncmp(name, prefix, prefix_len) != 0) { + return ERR_KCM_WRONG_CCNAME_FORMAT; + } + return EOK; +} diff --git a/src/responder/kcm/kcmsrv_ccache.h b/src/responder/kcm/kcmsrv_ccache.h new file mode 100644 index 0000000..1061ee2 --- /dev/null +++ b/src/responder/kcm/kcmsrv_ccache.h @@ -0,0 +1,380 @@ +/* + SSSD + + KCM Server - the KCM ccache operations + + Copyright (C) Red Hat, 2016 + + 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/>. +*/ +#ifndef _KCMSRV_CCACHE_H_ +#define _KCMSRV_CCACHE_H_ + +#include "config.h" + +#include <krb5/krb5.h> +#include <uuid/uuid.h> + +#include "util/util.h" +#include "util/sss_iobuf.h" +#include "util/util_creds.h" +#include "providers/krb5/krb5_common.h" +#include "responder/kcm/kcmsrv_pvt.h" + +#define UUID_BYTES 16 +#define UUID_STR_SIZE 37 + +/* Just to keep the name of the ccache readable */ +#define MAX_CC_NUM 99999 + +/* + * Credentials are opaque to the KCM server + * + * Each ccache has a unique UUID. + */ +struct kcm_cred; + +/* + * An opaque ccache type and its operations + * + * Contains zero or some KCM credentials. One credential in the cache + * is marked as the default one. The client can set and get the default + * cache (e.g. with kswitch) but one cache is always the default -- we + * fall back to the one created first. + * + * Each cache has a name and a UUID. Heimdal allows the name to be changed, + * we don't (yet, because the MIT client doesn't allow that either) + * + * Each ccache also stores a client principal. + */ +struct kcm_ccache; + +/* + * Create a new KCM ccache owned by mem_ctx on the + * memory level. + * + * When created, the ccache contains no credentials + */ +errno_t kcm_cc_new(TALLOC_CTX *mem_ctx, + krb5_context k5c, + struct cli_creds *owner, + const char *name, + krb5_principal princ, + struct kcm_ccache **_cc); + +/* + * Duplicate the ccache. Only ccache and credentials are duplicated, + * but their data are a shallow copy. + */ +struct kcm_ccache *kcm_cc_dup(TALLOC_CTX *mem_ctx, + const struct kcm_ccache *cc); + +/* + * Returns true if a client can access a ccache. + * + * Note that root can access any ccache */ +bool kcm_cc_access(struct kcm_ccache *cc, + struct cli_creds *client); + +/* + * Since the kcm_ccache structure is opaque, the kcmsrv_ccache + * layer contains a number of getsetters to read and write + * properties of the kcm_ccache structure + */ +const char *kcm_cc_get_name(struct kcm_ccache *cc); +errno_t kcm_cc_get_uuid(struct kcm_ccache *cc, uuid_t _uuid); +krb5_principal kcm_cc_get_client_principal(struct kcm_ccache *cc); +int32_t kcm_cc_get_offset(struct kcm_ccache *cc); + +/* Mainly useful for creating a cred structure from a persistent + * storage + */ +struct kcm_cred *kcm_cred_new(TALLOC_CTX *mem_ctx, + uuid_t uuid, + struct sss_iobuf *cred_blob); + +/* Add a cred to ccache */ +errno_t kcm_cc_store_creds(struct kcm_ccache *cc, + struct kcm_cred *crd); + +/* Set cc header information from sec key and client */ +errno_t kcm_cc_set_header(struct kcm_ccache *cc, + const char *sec_key, + struct cli_creds *client); + +krb5_creds **kcm_cc_unmarshal(TALLOC_CTX *mem_ctx, + krb5_context krb_context, + struct kcm_ccache *cc); + +errno_t kcm_cred_get_uuid(struct kcm_cred *crd, uuid_t uuid); + +/* + * At the moment, the credentials are stored without unmarshalling + * them, just as the clients sends the credentials. + */ +struct sss_iobuf *kcm_cred_get_creds(struct kcm_cred *crd); +errno_t kcm_cc_store_cred_blob(struct kcm_ccache *cc, + struct sss_iobuf *cred_blob); + /* + * The KCM server can call kcm_cred_get_creds to fetch the first + * credential, then iterate over the credentials with + * kcm_cc_next_cred until it returns NULL + */ +struct kcm_cred *kcm_cc_get_cred(struct kcm_ccache *cc); +struct kcm_cred *kcm_cc_next_cred(struct kcm_cred *crd); + +/* An opaque database that contains all the ccaches */ +struct kcm_ccdb; + +/* + * Initialize a ccache database of type cc_be + */ +struct kcm_ccdb *kcm_ccdb_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct confdb_ctx *cdb, + const char *confdb_service_path, + enum kcm_ccdb_be cc_be); +/* + * Prepare KCM ccache list for renewals + */ +errno_t kcm_ccdb_renew_tgts(TALLOC_CTX *mem_ctx, + struct krb5_ctx *kctx, + struct tevent_context *ev, + struct kcm_ccdb *cdb, + struct kcm_ccache ***_cc_list); + +/* + * In KCM, each ccache name is usually in the form of "UID:<num> + * + * The <num> is generated by the KCM ccache database. Use this function + * to retrieve the next number + */ +struct tevent_req *kcm_ccdb_nextid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client); +errno_t kcm_ccdb_nextid_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + char **_nextid); + +/* + * List all ccaches that belong to a given client + * + * The cc_list the recv function returns is NULL-terminated. + * + * NOTE: Contrary to how Heimdal behaves, root CAN NOT list all ccaches + * of all users. This is a deliberate decision to treat root as any other + * user, except it can access a ccache of another user by name, just not + * list them. + * + * If a client has no ccaches, the function returns OK, but an empty list + * containing just the NULL sentinel. + */ +struct tevent_req *kcm_ccdb_list_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client); +errno_t kcm_ccdb_list_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uuid_t **_uuid_list); + +/* + * Retrieve a ccache by name. + * + * If there is no such ccache, return EOK, but a NULL _cc pointer + */ +struct tevent_req *kcm_ccdb_getbyname_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + const char *name); +errno_t kcm_ccdb_getbyname_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct kcm_ccache **_cc); + +/* + * Retrieve a ccache by UUID + * + * If there is no such ccache, return EOK, but a NULL _cc pointer + */ +struct tevent_req *kcm_ccdb_getbyuuid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid); +errno_t kcm_ccdb_getbyuuid_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct kcm_ccache **_cc); + +/* + * Retrieve the default ccache. If there is no default cache, + * return EOK, but a NULL UUID. + */ +struct tevent_req *kcm_ccdb_get_default_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client); +errno_t kcm_ccdb_get_default_recv(struct tevent_req *req, + uuid_t *uuid); + +/* + * Translating name to UUID is often considerably faster than doing a full + * CC retrieval, hence this function and the converse. If the UUID cannot + * be found in the database, return ERR_KCM_CC_END + */ +struct tevent_req *kcm_ccdb_name_by_uuid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid); +errno_t kcm_ccdb_name_by_uuid_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + const char **_name); + +/* + * Translating UUID to name is often considerably faster than doing a full + * CC retrieval, hence this function and the converse. If the UUID cannot + * be found in the database, return ERR_KCM_CC_END + */ +struct tevent_req *kcm_ccdb_uuid_by_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + const char *name); +errno_t kcm_ccdb_uuid_by_name_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uuid_t _uuid); + +/* + * Set the default ccache. Passing a NULL UUID is a legal operation + * that 'unsets' the default ccache. + */ +struct tevent_req *kcm_ccdb_set_default_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid); +errno_t kcm_ccdb_set_default_recv(struct tevent_req *req); + +/* + * Add a ccache to the database. + */ +struct tevent_req *kcm_ccdb_create_cc_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + struct kcm_ccache *cc); +errno_t kcm_ccdb_create_cc_recv(struct tevent_req *req); + +/* + * Modify cache properties in a db + */ +struct kcm_mod_ctx { + int32_t kdc_offset; + krb5_principal client; + /* More settable properties (like name, when we support renames + * will be added later + */ +}; + +struct kcm_mod_ctx *kcm_mod_ctx_new(TALLOC_CTX *mem_ctx); +errno_t kcm_mod_cc(struct kcm_ccache *cc, struct kcm_mod_ctx *mod_ctx); + +struct tevent_req *kcm_ccdb_mod_cc_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid, + struct kcm_mod_ctx *mod_cc); +errno_t kcm_ccdb_mod_cc_recv(struct tevent_req *req); + +/* + * Store a credential in a cache + */ +struct tevent_req *kcm_ccdb_store_cred_blob_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid, + struct sss_iobuf *cred_blob); +errno_t kcm_ccdb_store_cred_blob_recv(struct tevent_req *req); + +/* + * Delete a ccache from the database + */ +struct tevent_req *kcm_ccdb_delete_cc_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid); +errno_t kcm_ccdb_delete_cc_recv(struct tevent_req *req); + +void kcm_debug_uuid(uuid_t uuid); + +/* + * The KCM clients are not allowed (except root) to create ccaches + * with arbitrary names. Instead, we assert that the ccache name + * begins with UID where UID is the stringified representation of + * the client's UID number + */ +errno_t kcm_check_name(const char *name, struct cli_creds *client); + +/* + * ccahe marshalling to and from JSON. This is used when the ccaches + * are stored in the secrets store + */ + +/* + * The secrets store is a key-value store at heart. We store the UUID + * and the name in the key to allow easy lookups be either key + */ +bool sec_key_match_name(const char *sec_key, + const char *name); + +bool sec_key_match_uuid(const char *sec_key, + uuid_t uuid); + +errno_t sec_key_parse(TALLOC_CTX *mem_ctx, + const char *sec_key, + const char **_name, + uuid_t uuid); + +const char *sec_key_get_name(const char *sec_key); + +errno_t sec_key_get_uuid(const char *sec_key, + uuid_t uuid); + +const char *sec_key_create(TALLOC_CTX *mem_ctx, + const char *name, + uuid_t uuid); + +/* + * sec_key is a concatenation of the ccache's UUID and name + * sec_value is the binary representation of ccache. + */ +errno_t sec_kv_to_ccache_binary(TALLOC_CTX *mem_ctx, + const char *sec_key, + struct sss_iobuf *sec_value, + struct cli_creds *client, + struct kcm_ccache **_cc); + +/* Convert a kcm_ccache to its binary representation. */ +errno_t kcm_ccache_to_sec_input_binary(TALLOC_CTX *mem_ctx, + struct kcm_ccache *cc, + struct sss_iobuf **_payload); + +errno_t bin_to_krb_data(TALLOC_CTX *mem_ctx, + struct sss_iobuf *buf, + krb5_data *out); +#endif /* _KCMSRV_CCACHE_H_ */ diff --git a/src/responder/kcm/kcmsrv_ccache_be.h b/src/responder/kcm/kcmsrv_ccache_be.h new file mode 100644 index 0000000..78d314e --- /dev/null +++ b/src/responder/kcm/kcmsrv_ccache_be.h @@ -0,0 +1,216 @@ +/* + SSSD + + KCM Server - the KCM ccache database interface + + This file should only be included from the ccache.c module. + + Copyright (C) Red Hat, 2016 + + 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/>. +*/ + +#ifndef _KCMSRV_CCACHE_BE_ +#define _KCMSRV_CCACHE_BE_ + +#include "config.h" + +#include <talloc.h> +#include "responder/kcm/kcmsrv_ccache.h" + +typedef errno_t +(*ccdb_init_fn)(struct kcm_ccdb *db, + struct confdb_ctx *cdb, + const char *confdb_service_path); + +typedef struct tevent_req * +(*ccdb_nextid_send_fn)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client); +typedef errno_t +(*ccdb_nextid_recv_fn)(struct tevent_req *req, + unsigned int *_nextid); + +typedef struct tevent_req * +(*ccdb_set_default_send_fn)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid); +typedef errno_t +(*ccdb_set_default_recv_fn)(struct tevent_req *req); + +typedef struct tevent_req * +(*ccdb_get_default_send_fn)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client); +typedef errno_t +(*ccdb_get_default_recv_fn)(struct tevent_req *req, + uuid_t dfl); + + +typedef errno_t +(*ccdb_list_all_cc_fn)(TALLOC_CTX *mem_ctx, + struct krb5_ctx *kctx, + struct tevent_context *ev, + struct kcm_ccdb *cdb, + struct kcm_ccache ***_cc_list); + +typedef struct tevent_req * +(*ccdb_list_send_fn)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client); +typedef errno_t +(*ccdb_list_recv_fn)(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uuid_t **_uuid_list); + +typedef struct tevent_req * +(*ccdb_getbyname_send_fn)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + const char *name); +typedef errno_t +(*ccdb_getbyname_recv_fn)(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct kcm_ccache **_cc); + +typedef struct tevent_req * +(*ccdb_getbyuuid_send_fn)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid); +typedef errno_t +(*ccdb_getbyuuid_recv_fn)(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct kcm_ccache **_cc); + +typedef struct tevent_req * +(*ccdb_name_by_uuid_send_fn)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid); +typedef errno_t +(*ccdb_name_by_uuid_recv_fn)(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + const char **_name); + +typedef struct tevent_req * +(*ccdb_uuid_by_name_send_fn)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + const char *name); +typedef errno_t +(*ccdb_uuid_by_name_recv_fn)(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uuid_t _uuid); + +typedef struct tevent_req * +(*ccdb_create_send_fn)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + struct kcm_ccache *cc); +typedef errno_t +(*ccdb_create_recv_fn)(struct tevent_req *req); + +typedef struct tevent_req * +(*ccdb_mod_send_fn)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid, + struct kcm_mod_ctx *mod_cc); +typedef errno_t +(*ccdb_mod_recv_fn)(struct tevent_req *req); + +typedef struct tevent_req * +(*kcm_ccdb_store_cred_blob_send_fn)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid, + struct sss_iobuf *cred_blob); +typedef errno_t +(*kcm_ccdb_store_cred_blob_recv_fn)(struct tevent_req *req); + +typedef struct tevent_req * +(*ccdb_delete_send_fn)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid); +typedef errno_t +(*ccdb_delete_recv_fn)(struct tevent_req *req); + +/* + * Each ccache back end (for example memory or secdb) must implement + * all these functions. The functions are wrapped by the kcm_ccdb + * interface that performs additional sanity checks or contains shared + * logic such as access checks but in general doesn't assume anything + * about how the operations work. + */ +struct kcm_ccdb_ops { + ccdb_init_fn init; + + ccdb_nextid_send_fn nextid_send; + ccdb_nextid_recv_fn nextid_recv; + + ccdb_set_default_send_fn set_default_send; + ccdb_set_default_recv_fn set_default_recv; + + ccdb_get_default_send_fn get_default_send; + ccdb_get_default_recv_fn get_default_recv; + + ccdb_list_all_cc_fn list_all_cc; + + ccdb_list_send_fn list_send; + ccdb_list_recv_fn list_recv; + + ccdb_getbyname_send_fn getbyname_send; + ccdb_getbyname_recv_fn getbyname_recv; + + ccdb_getbyuuid_send_fn getbyuuid_send; + ccdb_getbyuuid_recv_fn getbyuuid_recv; + + ccdb_name_by_uuid_send_fn name_by_uuid_send; + ccdb_name_by_uuid_recv_fn name_by_uuid_recv; + + ccdb_uuid_by_name_send_fn uuid_by_name_send; + ccdb_uuid_by_name_recv_fn uuid_by_name_recv; + + ccdb_create_send_fn create_send; + ccdb_create_recv_fn create_recv; + + ccdb_mod_send_fn mod_send; + ccdb_mod_recv_fn mod_recv; + + kcm_ccdb_store_cred_blob_send_fn store_cred_send; + kcm_ccdb_store_cred_blob_recv_fn store_cred_recv; + + ccdb_delete_send_fn delete_send; + ccdb_delete_recv_fn delete_recv; +}; + +extern const struct kcm_ccdb_ops ccdb_mem_ops; +extern const struct kcm_ccdb_ops ccdb_secdb_ops; + +#endif /* _KCMSRV_CCACHE_BE_ */ diff --git a/src/responder/kcm/kcmsrv_ccache_binary.c b/src/responder/kcm/kcmsrv_ccache_binary.c new file mode 100644 index 0000000..cc4191a --- /dev/null +++ b/src/responder/kcm/kcmsrv_ccache_binary.c @@ -0,0 +1,308 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2020 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 "config.h" + +#include <stdio.h> +#include <talloc.h> + +#include "util/util.h" +#include "util/util_creds.h" +#include "util/crypto/sss_crypto.h" +#include "responder/kcm/kcmsrv_ccache_pvt.h" + +static errno_t krb_data_to_bin(krb5_data *data, struct sss_iobuf *buf) +{ + return sss_iobuf_write_varlen(buf, (uint8_t *)data->data, data->length); +} + +static errno_t princ_to_bin(krb5_principal princ, struct sss_iobuf *buf) +{ + errno_t ret; + + if (princ == NULL) { + return sss_iobuf_write_uint8(buf, 0); + } + + /* Mark that principal is not empty. */ + ret = sss_iobuf_write_uint8(buf, 1); + if (ret != EOK) { + return ret; + } + + ret = krb_data_to_bin(&princ->realm, buf); + if (ret != EOK) { + return ret; + } + + ret = sss_iobuf_write_int32(buf, princ->type); + if (ret != EOK) { + return ret; + } + + ret = sss_iobuf_write_int32(buf, princ->length); + if (ret != EOK) { + return ret; + } + + for (krb5_int32 i = 0; i < princ->length; i++) { + ret = krb_data_to_bin(&princ->data[i], buf); + if (ret != EOK) { + return ret; + } + } + + return EOK; +} + +static errno_t creds_to_bin(struct kcm_cred *creds, struct sss_iobuf *buf) +{ + struct kcm_cred *crd; + uint32_t count = 0; + errno_t ret; + + DLIST_FOR_EACH(crd, creds) { + count++; + } + + ret = sss_iobuf_write_uint32(buf, count); + if (ret != EOK) { + return ret; + } + + DLIST_FOR_EACH(crd, creds) { + ret = sss_iobuf_write_len(buf, (uint8_t *)crd->uuid, sizeof(uuid_t)); + if (ret != EOK) { + return ret; + } + + ret = sss_iobuf_write_iobuf(buf, crd->cred_blob); + if (ret != EOK) { + return ret; + } + } + + return EOK; +} + +errno_t kcm_ccache_to_sec_input_binary(TALLOC_CTX *mem_ctx, + struct kcm_ccache *cc, + struct sss_iobuf **_payload) +{ + struct sss_iobuf *buf; + errno_t ret; + + buf = sss_iobuf_init_empty(mem_ctx, sizeof(krb5_principal_data), 0); + if (buf == NULL) { + return ENOMEM; + } + + ret = sss_iobuf_write_int32(buf, cc->kdc_offset); + if (ret != EOK) { + goto done; + } + + ret = princ_to_bin(cc->client, buf); + if (ret != EOK) { + goto done; + } + + ret = creds_to_bin(cc->creds, buf); + if (ret != EOK) { + goto done; + } + + *_payload = buf; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(buf); + } + + return ret; +} + +errno_t bin_to_krb_data(TALLOC_CTX *mem_ctx, + struct sss_iobuf *buf, + krb5_data *out) +{ + uint8_t *data; + size_t len; + errno_t ret; + + ret = sss_iobuf_read_varlen(mem_ctx, buf, &data, &len); + if (ret != EOK) { + return ret; + } + + out->magic = 0; + out->data = (char*)data; + out->length = len; + + return EOK; +} + +static errno_t bin_to_princ(TALLOC_CTX *mem_ctx, + struct sss_iobuf *buf, + krb5_principal *_princ) +{ + krb5_principal princ; + uint8_t non_empty; + krb5_int32 i; + errno_t ret; + + ret = sss_iobuf_read_uint8(buf, &non_empty); + if (ret != EOK) { + return ret; + } + + if (non_empty == 0) { + *_princ = NULL; + return EOK; + } + + princ = talloc_zero(mem_ctx, struct krb5_principal_data); + if (princ == NULL) { + return ENOMEM; + } + princ->magic = KV5M_PRINCIPAL; + + ret = bin_to_krb_data(princ, buf, &princ->realm); + if (ret != EOK) { + return ret; + } + + ret = sss_iobuf_read_int32(buf, &princ->type); + if (ret != EOK) { + return ret; + } + + ret = sss_iobuf_read_int32(buf, &princ->length); + if (ret != EOK) { + return ret; + } + + princ->data = talloc_array(princ, krb5_data, princ->length); + if (princ->length > 0 && princ->data == NULL) { + return ENOMEM; + } + + for (i = 0; i < princ->length; i++) { + ret = bin_to_krb_data(princ, buf, &princ->data[i]); + if (ret != EOK) { + return ret; + } + } + + *_princ = princ; + + return EOK; +} + +static errno_t bin_to_creds(TALLOC_CTX *mem_ctx, + struct sss_iobuf *buf, + struct kcm_cred **_creds) +{ + struct kcm_cred *creds = NULL; + struct kcm_cred *crd; + struct sss_iobuf *cred_blob; + uint32_t count; + uuid_t uuid; + errno_t ret; + + ret = sss_iobuf_read_uint32(buf, &count); + if (ret != EOK) { + return ret; + } + + for (uint32_t i = 0; i < count; i++) { + ret = sss_iobuf_read_len(buf, sizeof(uuid_t), (uint8_t*)uuid); + if (ret != EOK) { + return ret; + } + + ret = sss_iobuf_read_iobuf(NULL, buf, &cred_blob); + if (ret != EOK) { + return ret; + } + + crd = kcm_cred_new(mem_ctx, uuid, cred_blob); + if (crd == NULL) { + talloc_free(cred_blob); + return ENOMEM; + } + + DLIST_ADD(creds, crd); + } + + *_creds = creds; + + return EOK; +} + +errno_t sec_kv_to_ccache_binary(TALLOC_CTX *mem_ctx, + const char *sec_key, + struct sss_iobuf *sec_value, + struct cli_creds *client, + struct kcm_ccache **_cc) +{ + struct kcm_ccache *cc; + errno_t ret; + + cc = talloc_zero(mem_ctx, struct kcm_ccache); + if (cc == NULL) { + return ENOMEM; + } + + ret = kcm_cc_set_header(cc, sec_key, client); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot store ccache header [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = sss_iobuf_read_int32(sec_value, &cc->kdc_offset); + if (ret != EOK) { + goto done; + } + + ret = bin_to_princ(cc, sec_value, &cc->client); + if (ret != EOK) { + goto done; + } + + ret = bin_to_creds(cc, sec_value, &cc->creds); + if (ret != EOK) { + goto done; + } + + *_cc = cc; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(cc); + } + + return ret; +} diff --git a/src/responder/kcm/kcmsrv_ccache_key.c b/src/responder/kcm/kcmsrv_ccache_key.c new file mode 100644 index 0000000..59d6045 --- /dev/null +++ b/src/responder/kcm/kcmsrv_ccache_key.c @@ -0,0 +1,144 @@ +/* + SSSD + + Copyright (C) Red Hat, 2020 + + 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 "config.h" + +#include <stdio.h> +#include <talloc.h> + +#include "util/util.h" +#include "responder/kcm/kcmsrv_ccache_pvt.h" + +/* + * The secrets store is a key-value store at heart. We store the UUID + * and the name in the key to allow easy lookups by either part. + */ +#define SEC_KEY_SEPARATOR '-' + +const char *sec_key_create(TALLOC_CTX *mem_ctx, + const char *name, + uuid_t uuid) +{ + char uuid_str[UUID_STR_SIZE]; + + uuid_unparse(uuid, uuid_str); + return talloc_asprintf(mem_ctx, + "%s%c%s", uuid_str, SEC_KEY_SEPARATOR, name); +} + +static bool sec_key_valid(const char *sec_key) +{ + if (sec_key == NULL) { + return false; + } + + if (strlen(sec_key) < UUID_STR_SIZE + 1) { + /* One char for separator (at UUID_STR_SIZE, because strlen doesn't + * include the '\0', but UUID_STR_SIZE does) and at least one for + * the name */ + DEBUG(SSSDBG_CRIT_FAILURE, "Key %s is too short\n", sec_key); + return false; + } + + if (sec_key[UUID_STR_SIZE - 1] != SEC_KEY_SEPARATOR) { + DEBUG(SSSDBG_CRIT_FAILURE, "Key doesn't contain the separator\n"); + return false; + } + + return true; +} + +errno_t sec_key_parse(TALLOC_CTX *mem_ctx, + const char *sec_key, + const char **_name, + uuid_t uuid) +{ + char uuid_str[UUID_STR_SIZE]; + + if (!sec_key_valid(sec_key)) { + return EINVAL; + } + + strncpy(uuid_str, sec_key, sizeof(uuid_str) - 1); + if (sec_key[UUID_STR_SIZE - 1] != SEC_KEY_SEPARATOR) { + DEBUG(SSSDBG_CRIT_FAILURE, "Key doesn't contain the separator\n"); + return EINVAL; + } + uuid_str[UUID_STR_SIZE - 1] = '\0'; + + *_name = talloc_strdup(mem_ctx, sec_key + UUID_STR_SIZE); + if (*_name == NULL) { + return ENOMEM; + } + uuid_parse(uuid_str, uuid); + + return EOK; +} + +errno_t sec_key_get_uuid(const char *sec_key, + uuid_t uuid) +{ + char uuid_str[UUID_STR_SIZE]; + + if (!sec_key_valid(sec_key)) { + return EINVAL; + } + + strncpy(uuid_str, sec_key, UUID_STR_SIZE - 1); + uuid_str[UUID_STR_SIZE - 1] = '\0'; + uuid_parse(uuid_str, uuid); + return EOK; +} + +const char *sec_key_get_name(const char *sec_key) +{ + if (!sec_key_valid(sec_key)) { + return NULL; + } + + return sec_key + UUID_STR_SIZE; +} + +bool sec_key_match_name(const char *sec_key, + const char *name) +{ + if (!sec_key_valid(sec_key) || name == NULL) { + return false; + } + + return strcmp(sec_key + UUID_STR_SIZE, name) == 0; +} + +bool sec_key_match_uuid(const char *sec_key, + uuid_t uuid) +{ + errno_t ret; + uuid_t key_uuid; + + /* Clear uuid value to avoid cppcheck warning. */ + uuid_clear(key_uuid); + + ret = sec_key_get_uuid(sec_key, key_uuid); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Cannot convert key to UUID\n"); + return false; + } + + return uuid_compare(key_uuid, uuid) == 0; +} diff --git a/src/responder/kcm/kcmsrv_ccache_mem.c b/src/responder/kcm/kcmsrv_ccache_mem.c new file mode 100644 index 0000000..0e3a7b2 --- /dev/null +++ b/src/responder/kcm/kcmsrv_ccache_mem.c @@ -0,0 +1,826 @@ +/* + SSSD + + KCM Server - ccache in-memory storage + + Copyright (C) Red Hat, 2016 + + 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 "config.h" + +#include <talloc.h> +#include <stdio.h> + +#include "util/util.h" +#include "responder/kcm/kcmsrv_ccache_pvt.h" +#include "responder/kcm/kcmsrv_ccache_be.h" + +struct ccdb_mem; + +/* + * The KCM memory database is just a double-linked list of kcm_ccache structures + */ +struct ccache_mem_wrap { + struct kcm_ccache *cc; + bool is_default; + + struct ccache_mem_wrap *next; + struct ccache_mem_wrap *prev; + + struct ccdb_mem *mem_be; +}; + +struct ccdb_mem { + /* Both ccaches and the next-id are kept in memory */ + struct ccache_mem_wrap *head; + unsigned int nextid; +}; + +static struct ccache_mem_wrap *memdb_get_by_uuid(struct ccdb_mem *memdb, + struct cli_creds *client, + uuid_t uuid) +{ + uid_t uid; + struct ccache_mem_wrap *ccwrap = NULL; + struct ccache_mem_wrap *out = NULL; + + uid = cli_creds_get_uid(client); + + DLIST_FOR_EACH(ccwrap, memdb->head) { + if (ccwrap->cc == NULL) { + /* since KCM stores ccaches, better not crash.. */ + DEBUG(SSSDBG_CRIT_FAILURE, "BUG: ccwrap contains NULL cc\n"); + continue; + } + + if (ccwrap->cc->owner.uid == uid) { + if (uuid_compare(uuid, ccwrap->cc->uuid) == 0) { + out = ccwrap; + break; + } + } + } + + return out; +} + +static struct ccache_mem_wrap *memdb_get_by_name(struct ccdb_mem *memdb, + struct cli_creds *client, + const char *name) +{ + uid_t uid; + struct ccache_mem_wrap *ccwrap = NULL; + struct ccache_mem_wrap *out = NULL; + + uid = cli_creds_get_uid(client); + + DLIST_FOR_EACH(ccwrap, memdb->head) { + if (ccwrap->cc == NULL) { + /* since KCM stores ccaches, better not crash.. */ + DEBUG(SSSDBG_CRIT_FAILURE, "BUG: ccwrap contains NULL cc\n"); + continue; + } + + if (ccwrap->cc->owner.uid == uid) { + if (strcmp(ccwrap->cc->name, name) == 0) { + out = ccwrap; + break; + } + } + } + + return out; +} + +/* Since with the in-memory database, the database operations are just + * fake-async wrappers around otherwise sync operations, we don't often + * need any state, so we use this empty structure instead + */ +struct ccdb_mem_dummy_state { +}; + +static int ccwrap_destructor(void *ptr) +{ + struct ccache_mem_wrap *ccwrap = talloc_get_type(ptr, struct ccache_mem_wrap); + + if (ccwrap == NULL) { + return 0; + } + + if (ccwrap->cc != NULL) { + if (ccwrap->cc->creds) { + sss_erase_mem_securely( + sss_iobuf_get_data(ccwrap->cc->creds->cred_blob), + sss_iobuf_get_size(ccwrap->cc->creds->cred_blob)); + } + } + + + DLIST_REMOVE(ccwrap->mem_be->head, ccwrap); + + return 0; +} + +static errno_t ccdb_mem_init(struct kcm_ccdb *db, + struct confdb_ctx *cdb, + const char *confdb_service_path) +{ + struct ccdb_mem *memdb = NULL; + + memdb = talloc_zero(db, struct ccdb_mem); + if (memdb == NULL) { + return ENOMEM; + } + db->db_handle = memdb; + + return EOK; +} + +struct ccdb_mem_nextid_state { + unsigned int nextid; +}; + +static struct tevent_req *ccdb_mem_nextid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client) +{ + struct tevent_req *req = NULL; + struct ccdb_mem_nextid_state *state = NULL; + struct ccdb_mem *memdb = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ccdb_mem_nextid_state); + if (req == NULL) { + return NULL; + } + + memdb = talloc_get_type(db->db_handle, struct ccdb_mem); + if (memdb == NULL) { + ret = EIO; + goto immediate; + } + + state->nextid = memdb->nextid++; + + ret = EOK; +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ccdb_mem_nextid_recv(struct tevent_req *req, + unsigned int *_nextid) +{ + struct ccdb_mem_nextid_state *state = tevent_req_data(req, + struct ccdb_mem_nextid_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + *_nextid = state->nextid; + return EOK; +} + +struct ccdb_mem_list_state { + uuid_t *uuid_list; +}; + +static struct tevent_req *ccdb_mem_list_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client) +{ + struct tevent_req *req = NULL; + struct ccache_mem_wrap *ccwrap = NULL; + struct ccdb_mem_list_state *state = NULL; + struct ccdb_mem *memdb = talloc_get_type(db->db_handle, struct ccdb_mem); + size_t num_ccaches = 0; + size_t cc_index = 0; + errno_t ret; + uid_t uid; + + req = tevent_req_create(mem_ctx, &state, struct ccdb_mem_list_state); + if (req == NULL) { + return NULL; + } + + uid = cli_creds_get_uid(client); + + DLIST_FOR_EACH(ccwrap, memdb->head) { + if (ccwrap->cc->owner.uid == uid) { + num_ccaches++; + } + } + + state->uuid_list = talloc_zero_array(state, uuid_t, num_ccaches+1); + if (state->uuid_list == NULL) { + ret = ENOMEM; + goto immediate; + } + + cc_index = 0; + DLIST_FOR_EACH(ccwrap, memdb->head) { + if (ccwrap->cc->owner.uid == uid) { + uuid_copy(state->uuid_list[cc_index], ccwrap->cc->uuid); + cc_index++; + } + } + uuid_clear(state->uuid_list[num_ccaches]); + + ret = EOK; +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ccdb_mem_list_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uuid_t **_uuid_list) +{ + struct ccdb_mem_list_state *state = tevent_req_data(req, + struct ccdb_mem_list_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + *_uuid_list = talloc_steal(mem_ctx, state->uuid_list); + return EOK; +} + +static struct tevent_req *ccdb_mem_set_default_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid) +{ + struct tevent_req *req = NULL; + struct ccdb_mem_dummy_state *state = NULL; + struct ccdb_mem *memdb = talloc_get_type(db->db_handle, struct ccdb_mem); + struct ccache_mem_wrap *ccwrap = NULL; + uid_t uid = cli_creds_get_uid(client); + + req = tevent_req_create(mem_ctx, &state, struct ccdb_mem_dummy_state); + if (req == NULL) { + return NULL; + } + + /* Reset all ccache defaults first */ + DLIST_FOR_EACH(ccwrap, memdb->head) { + if (ccwrap->cc == NULL) { + /* since KCM stores ccaches, better not crash.. */ + DEBUG(SSSDBG_CRIT_FAILURE, "BUG: ccwrap contains NULL cc\n"); + continue; + } + + if (ccwrap->cc->owner.uid == uid) { + ccwrap->is_default = false; + } + } + + /* Then set the default for the right ccache. This also allows to + * pass a null uuid to just reset the old ccache (for example after + * deleting the default + */ + ccwrap = memdb_get_by_uuid(memdb, client, uuid); + if (ccwrap != NULL) { + ccwrap->is_default = true; + } + + tevent_req_done(req); + tevent_req_post(req, ev); + return req; +} + +static errno_t ccdb_mem_set_default_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +struct ccdb_mem_get_default_state { + uuid_t dfl_uuid; +}; + +static struct tevent_req *ccdb_mem_get_default_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client) +{ + struct tevent_req *req = NULL; + struct ccdb_mem_get_default_state *state = NULL; + struct ccache_mem_wrap *ccwrap = NULL; + struct ccdb_mem *memdb = talloc_get_type(db->db_handle, struct ccdb_mem); + uid_t uid = cli_creds_get_uid(client); + + req = tevent_req_create(mem_ctx, &state, struct ccdb_mem_get_default_state); + if (req == NULL) { + return NULL; + } + + + /* Reset all ccache defaults first */ + DLIST_FOR_EACH(ccwrap, memdb->head) { + if (ccwrap->cc == NULL) { + /* since KCM stores ccaches, better not crash.. */ + DEBUG(SSSDBG_CRIT_FAILURE, "BUG: ccwrap contains NULL cc\n"); + continue; + } + + if (ccwrap->cc->owner.uid == uid && ccwrap->is_default == true) { + break; + } + } + + if (ccwrap == NULL) { + DEBUG(SSSDBG_TRACE_FUNC, + "No ccache marked as default, returning null ccache\n"); + uuid_clear(state->dfl_uuid); + } else { + uuid_copy(state->dfl_uuid, ccwrap->cc->uuid); + } + + tevent_req_done(req); + tevent_req_post(req, ev); + return req; +} + +static errno_t ccdb_mem_get_default_recv(struct tevent_req *req, + uuid_t dfl) +{ + struct ccdb_mem_get_default_state *state = tevent_req_data(req, + struct ccdb_mem_get_default_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + uuid_copy(dfl, state->dfl_uuid); + return EOK; +} + +struct ccdb_mem_getbyuuid_state { + struct kcm_ccache *cc; +}; + +static struct tevent_req *ccdb_mem_getbyuuid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid) +{ + struct tevent_req *req = NULL; + struct ccdb_mem_getbyuuid_state *state = NULL; + struct ccdb_mem *memdb = talloc_get_type(db->db_handle, struct ccdb_mem); + struct ccache_mem_wrap *ccwrap = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ccdb_mem_getbyuuid_state); + if (req == NULL) { + return NULL; + } + + ccwrap = memdb_get_by_uuid(memdb, client, uuid); + if (ccwrap != NULL) { + /* In order to provide a consistent interface, we need to let the caller + * of getbyXXX own the ccache, therefore the memory back end returns a shallow + * copy of the ccache + */ + state->cc = kcm_cc_dup(state, ccwrap->cc); + if (state->cc == NULL) { + ret = ENOMEM; + goto immediate; + } + } + + ret = EOK; +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ccdb_mem_getbyuuid_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct kcm_ccache **_cc) +{ + struct ccdb_mem_getbyuuid_state *state = tevent_req_data(req, + struct ccdb_mem_getbyuuid_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + *_cc = talloc_steal(mem_ctx, state->cc); + return EOK; +} + +struct ccdb_mem_getbyname_state { + struct kcm_ccache *cc; +}; + +static struct tevent_req *ccdb_mem_getbyname_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + const char *name) +{ + struct tevent_req *req = NULL; + struct ccdb_mem_getbyname_state *state = NULL; + struct ccache_mem_wrap *ccwrap = NULL; + struct ccdb_mem *memdb = talloc_get_type(db->db_handle, struct ccdb_mem); + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ccdb_mem_getbyname_state); + if (req == NULL) { + return NULL; + } + + ccwrap = memdb_get_by_name(memdb, client, name); + if (ccwrap != NULL) { + /* In order to provide a consistent interface, we need to let the caller + * of getbyXXX own the ccache, therefore the memory back end returns a shallow + * copy of the ccache + */ + state->cc = kcm_cc_dup(state, ccwrap->cc); + if (state->cc == NULL) { + ret = ENOMEM; + goto immediate; + } + } + + ret = EOK; +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ccdb_mem_getbyname_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct kcm_ccache **_cc) +{ + struct ccdb_mem_getbyname_state *state = tevent_req_data(req, + struct ccdb_mem_getbyname_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + *_cc = talloc_steal(mem_ctx, state->cc); + return EOK; +} + +struct ccdb_mem_name_by_uuid_state { + const char *name; +}; + +struct tevent_req *ccdb_mem_name_by_uuid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid) +{ + struct tevent_req *req = NULL; + struct ccdb_mem_name_by_uuid_state *state = NULL; + struct ccdb_mem *memdb = talloc_get_type(db->db_handle, struct ccdb_mem); + struct ccache_mem_wrap *ccwrap = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ccdb_mem_name_by_uuid_state); + if (req == NULL) { + return NULL; + } + + ccwrap = memdb_get_by_uuid(memdb, client, uuid); + if (ccwrap == NULL) { + ret = ERR_KCM_CC_END; + goto immediate; + } + + state->name = talloc_strdup(state, ccwrap->cc->name); + if (state->name == NULL) { + ret = ENOMEM; + goto immediate; + } + + ret = EOK; +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +errno_t ccdb_mem_name_by_uuid_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + const char **_name) +{ + struct ccdb_mem_name_by_uuid_state *state = tevent_req_data(req, + struct ccdb_mem_name_by_uuid_state); + TEVENT_REQ_RETURN_ON_ERROR(req); + *_name = talloc_steal(mem_ctx, state->name); + return EOK; +} + +struct ccdb_mem_uuid_by_name_state { + uuid_t uuid; +}; + +struct tevent_req *ccdb_mem_uuid_by_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + const char *name) +{ + struct tevent_req *req = NULL; + struct ccdb_mem_uuid_by_name_state *state = NULL; + struct ccdb_mem *memdb = talloc_get_type(db->db_handle, struct ccdb_mem); + struct ccache_mem_wrap *ccwrap = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ccdb_mem_uuid_by_name_state); + if (req == NULL) { + return NULL; + } + + ccwrap = memdb_get_by_name(memdb, client, name); + if (ccwrap == NULL) { + ret = ERR_NO_CREDS; + goto immediate; + } + + uuid_copy(state->uuid, ccwrap->cc->uuid); + + ret = EOK; +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +errno_t ccdb_mem_uuid_by_name_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uuid_t _uuid) +{ + struct ccdb_mem_uuid_by_name_state *state = tevent_req_data(req, + struct ccdb_mem_uuid_by_name_state); + TEVENT_REQ_RETURN_ON_ERROR(req); + uuid_copy(_uuid, state->uuid); + return EOK; +} + +static struct tevent_req *ccdb_mem_create_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + struct kcm_ccache *cc) +{ + struct tevent_req *req = NULL; + struct ccdb_mem_dummy_state *state = NULL; + struct ccache_mem_wrap *ccwrap; + struct ccdb_mem *memdb = talloc_get_type(db->db_handle, struct ccdb_mem); + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ccdb_mem_dummy_state); + if (req == NULL) { + return NULL; + } + + ccwrap = talloc_zero(memdb, struct ccache_mem_wrap); + if (ccwrap == NULL) { + ret = ENOMEM; + goto immediate; + } + ccwrap->cc = cc; + ccwrap->mem_be = memdb; + talloc_steal(ccwrap, cc); + + DLIST_ADD(memdb->head, ccwrap); + talloc_set_destructor((TALLOC_CTX *) ccwrap, ccwrap_destructor); + + ret = EOK; +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ccdb_mem_create_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +static struct tevent_req *ccdb_mem_mod_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid, + struct kcm_mod_ctx *mod_cc) +{ + errno_t ret; + struct tevent_req *req = NULL; + struct ccdb_mem_dummy_state *state = NULL; + struct ccache_mem_wrap *ccwrap = NULL; + struct ccdb_mem *memdb = talloc_get_type(db->db_handle, struct ccdb_mem); + + req = tevent_req_create(mem_ctx, &state, struct ccdb_mem_dummy_state); + if (req == NULL) { + return NULL; + } + + /* UUID is immutable, so search by that */ + ccwrap = memdb_get_by_uuid(memdb, client, uuid); + if (ccwrap == NULL) { + ret = ERR_KCM_CC_END; + goto immediate; + } + + ret = kcm_mod_cc(ccwrap->cc, mod_cc); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot modify ccache [%d]: %s\n", + ret, sss_strerror(ret)); + goto immediate; + } + + ret = EOK; +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ccdb_mem_mod_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +static struct tevent_req *ccdb_mem_store_cred_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid, + struct sss_iobuf *cred_blob) +{ + struct tevent_req *req = NULL; + struct ccdb_mem_dummy_state *state = NULL; + struct ccdb_mem *memdb = talloc_get_type(db->db_handle, struct ccdb_mem); + struct ccache_mem_wrap *ccwrap = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ccdb_mem_dummy_state); + if (req == NULL) { + return NULL; + } + + ccwrap = memdb_get_by_uuid(memdb, client, uuid); + if (ccwrap == NULL) { + ret = ERR_KCM_CC_END; + goto immediate; + } + + ret = kcm_cc_store_cred_blob(ccwrap->cc, cred_blob); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot store credentials to ccache [%d]: %s\n", + ret, sss_strerror(ret)); + goto immediate; + } + + ret = EOK; +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ccdb_mem_store_cred_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +static struct tevent_req *ccdb_mem_delete_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid) +{ + struct tevent_req *req = NULL; + struct ccdb_mem_dummy_state *state = NULL; + struct ccache_mem_wrap *ccwrap; + struct ccdb_mem *memdb = talloc_get_type(db->db_handle, struct ccdb_mem); + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct ccdb_mem_dummy_state); + if (req == NULL) { + return NULL; + } + + ccwrap = memdb_get_by_uuid(memdb, client, uuid); + if (ccwrap == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, + "BUG: Attempting to free unknown ccache\n"); + ret = ERR_KCM_CC_END; + goto immediate; + } + + ret = EOK; + /* Destructor takes care of everything */ + talloc_free(ccwrap); +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ccdb_mem_delete_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +const struct kcm_ccdb_ops ccdb_mem_ops = { + .init = ccdb_mem_init, + + .nextid_send = ccdb_mem_nextid_send, + .nextid_recv = ccdb_mem_nextid_recv, + + .set_default_send = ccdb_mem_set_default_send, + .set_default_recv = ccdb_mem_set_default_recv, + + .get_default_send = ccdb_mem_get_default_send, + .get_default_recv = ccdb_mem_get_default_recv, + + .list_send = ccdb_mem_list_send, + .list_recv = ccdb_mem_list_recv, + + .getbyname_send = ccdb_mem_getbyname_send, + .getbyname_recv = ccdb_mem_getbyname_recv, + + .getbyuuid_send = ccdb_mem_getbyuuid_send, + .getbyuuid_recv = ccdb_mem_getbyuuid_recv, + + .name_by_uuid_send = ccdb_mem_name_by_uuid_send, + .name_by_uuid_recv = ccdb_mem_name_by_uuid_recv, + + .uuid_by_name_send = ccdb_mem_uuid_by_name_send, + .uuid_by_name_recv = ccdb_mem_uuid_by_name_recv, + + .create_send = ccdb_mem_create_send, + .create_recv = ccdb_mem_create_recv, + + .mod_send = ccdb_mem_mod_send, + .mod_recv = ccdb_mem_mod_recv, + + .store_cred_send = ccdb_mem_store_cred_send, + .store_cred_recv = ccdb_mem_store_cred_recv, + + .delete_send = ccdb_mem_delete_send, + .delete_recv = ccdb_mem_delete_recv, +}; diff --git a/src/responder/kcm/kcmsrv_ccache_pvt.h b/src/responder/kcm/kcmsrv_ccache_pvt.h new file mode 100644 index 0000000..9d5afed --- /dev/null +++ b/src/responder/kcm/kcmsrv_ccache_pvt.h @@ -0,0 +1,61 @@ +/* + SSSD + + KCM Server - the KCM ccache operations - private structures + + Should be accessed only from the ccache layer. + + Copyright (C) Red Hat, 2016 + + 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/>. +*/ +#ifndef _KCMSRV_CCACHE_PVT_H +#define _KCMSRV_CCACHE_PVT_H + +#include "responder/kcm/kcmsrv_ccache.h" +#include "responder/kcm/kcmsrv_ccache_be.h" + +struct kcm_ccache_owner { + uid_t uid; + gid_t gid; +}; + +struct kcm_cred { + struct sss_iobuf *cred_blob; + /* Randomly generated 16 bytes */ + uuid_t uuid; + + struct kcm_cred *next; + struct kcm_cred *prev; +}; + +struct kcm_ccdb { + struct tevent_context *ev; + + void *db_handle; + const struct kcm_ccdb_ops *ops; +}; + +struct kcm_ccache { + const char *name; + struct kcm_ccache_owner owner; + uuid_t uuid; + + krb5_principal client; + int32_t kdc_offset; + + struct kcm_cred *creds; +}; + +#endif /* _KCMSRV_CCACHE_PVT_H */ diff --git a/src/responder/kcm/kcmsrv_ccache_secdb.c b/src/responder/kcm/kcmsrv_ccache_secdb.c new file mode 100644 index 0000000..ff5e6e2 --- /dev/null +++ b/src/responder/kcm/kcmsrv_ccache_secdb.c @@ -0,0 +1,1671 @@ +/* + SSSD + + KCM Server - ccache storage using libsss_secrets + + Copyright (C) Red Hat, 2016 + + 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 "config.h" + +#include <talloc.h> +#include <stdio.h> + +#include "util/util.h" +#include "secrets/secrets.h" +#include "util/crypto/sss_crypto.h" +#include "util/sss_krb5.h" +#include "util/strtonum.h" +#include "responder/kcm/kcmsrv_ccache_pvt.h" +#include "responder/kcm/kcmsrv_ccache_be.h" +#include "responder/kcm/kcm_renew.h" +#include "providers/krb5/krb5_ccache.h" + +#define KCM_SECDB_URL "persistent" +#define KCM_SECDB_BASE_FMT KCM_SECDB_URL"/%"SPRIuid"/" +#define KCM_SECDB_CCACHE_FMT KCM_SECDB_BASE_FMT"ccache/" +#define KCM_SECDB_DFL_FMT KCM_SECDB_BASE_FMT"default" + +static errno_t sec_get(TALLOC_CTX *mem_ctx, + struct sss_sec_req *req, + struct sss_iobuf **_buf) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx; + uint8_t *data; + size_t len; + struct sss_iobuf *buf; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sss_sec_get(tmp_ctx, req, &data, &len); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot retrieve the secret [%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + buf = sss_iobuf_init_steal(tmp_ctx, data, len); + if (buf == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot init the iobuf\n"); + ret = EIO; + goto done; + } + + *_buf = talloc_steal(mem_ctx, buf); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t sec_put(TALLOC_CTX *mem_ctx, + struct sss_sec_req *req, + struct sss_iobuf *buf) +{ + errno_t ret; + + ret = sss_sec_put(req, sss_iobuf_get_data(buf), sss_iobuf_get_size(buf)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot write the secret [%d]: %s\n", ret, sss_strerror(ret)); + } + + return ret; +} + +static errno_t sec_update(TALLOC_CTX *mem_ctx, + struct sss_sec_req *req, + struct sss_iobuf *buf) +{ + errno_t ret; + + ret = sss_sec_update(req, sss_iobuf_get_data(buf), sss_iobuf_get_size(buf)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot write the secret [%d]: %s\n", ret, sss_strerror(ret)); + } + + return ret; +} + +static const char *secdb_container_url_create(TALLOC_CTX *mem_ctx, + struct cli_creds *client) +{ + return talloc_asprintf(mem_ctx, + KCM_SECDB_CCACHE_FMT, + cli_creds_get_uid(client)); +} + +static const char *secdb_cc_url_create(TALLOC_CTX *mem_ctx, + struct cli_creds *client, + const char *secdb_key) +{ + return talloc_asprintf(mem_ctx, + KCM_SECDB_CCACHE_FMT"%s", + cli_creds_get_uid(client), + secdb_key); +} + +static const char *secdb_dfl_url_create(TALLOC_CTX *mem_ctx, + struct cli_creds *client) +{ + return talloc_asprintf(mem_ctx, + KCM_SECDB_DFL_FMT, + cli_creds_get_uid(client)); +} + +static errno_t kcm_ccache_to_secdb_kv(TALLOC_CTX *mem_ctx, + struct kcm_ccache *cc, + struct cli_creds *client, + const char **_url, + struct sss_iobuf **_payload) +{ + errno_t ret; + const char *url; + const char *key; + TALLOC_CTX *tmp_ctx; + struct sss_iobuf *payload; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + key = sec_key_create(tmp_ctx, cc->name, cc->uuid); + if (key == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot create key for %s\n", cc->name); + ret = ENOMEM; + goto done; + } + + url = secdb_cc_url_create(tmp_ctx, client, key); + if (url == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot create URL from %s\n", key); + ret = ENOMEM; + goto done; + } + + ret = kcm_ccache_to_sec_input_binary(mem_ctx, cc, &payload); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot convert ccache to a secret [%d][%s]\n", ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; + DEBUG(SSSDBG_TRACE_INTERNAL, "Created URL %s\n", url); + *_url = talloc_steal(mem_ctx, url); + *_payload = talloc_steal(mem_ctx, payload); +done: + talloc_free(tmp_ctx); + return ret; +} + +struct ccdb_secdb { + struct sss_sec_ctx *sctx; +}; + +/* Since with the synchronous database, the database operations are just + * fake-async wrappers around otherwise sync operations, we don't often + * need any state structure, unless the _recv() function returns anything, + * so we use this empty structure instead + */ +struct ccdb_secdb_state { +}; + +static errno_t secdb_container_url_req(TALLOC_CTX *mem_ctx, + struct sss_sec_ctx *sctx, + struct cli_creds *client, + struct sss_sec_req **_sreq) +{ + const char *url; + struct sss_sec_req *sreq; + errno_t ret; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + url = secdb_container_url_create(tmp_ctx, client); + if (url == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sss_sec_new_req(tmp_ctx, sctx, url, geteuid(), &sreq); + if (ret != EOK) { + goto done; + } + + ret = EOK; + DEBUG(SSSDBG_TRACE_INTERNAL, "Created request for URL %s\n", url); + *_sreq = talloc_steal(mem_ctx, sreq); +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t secdb_cc_url_req(TALLOC_CTX *mem_ctx, + struct sss_sec_ctx *sctx, + struct cli_creds *client, + const char *secdb_url, + struct sss_sec_req **_sreq) +{ + struct sss_sec_req *sreq; + errno_t ret; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sss_sec_new_req(tmp_ctx, sctx, secdb_url, geteuid(), &sreq); + if (ret != EOK) { + goto done; + } + + ret = EOK; + DEBUG(SSSDBG_TRACE_INTERNAL, "Created request for URL %s\n", secdb_url); + *_sreq = talloc_steal(mem_ctx, sreq); +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t secdb_cc_key_req(TALLOC_CTX *mem_ctx, + struct sss_sec_ctx *sctx, + struct cli_creds *client, + const char *secdb_key, + struct sss_sec_req **_sreq) +{ + const char *url; + struct sss_sec_req *sreq; + errno_t ret; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + url = secdb_cc_url_create(tmp_ctx, client, secdb_key); + if (url == NULL) { + ret = ENOMEM; + goto done; + } + + ret = secdb_cc_url_req(tmp_ctx, sctx, client, url, &sreq); + if (ret != EOK) { + goto done; + } + + ret = EOK; + DEBUG(SSSDBG_TRACE_INTERNAL, "Created request for URL %s\n", url); + *_sreq = talloc_steal(mem_ctx, sreq); +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t secdb_dfl_url_req(TALLOC_CTX *mem_ctx, + struct sss_sec_ctx *sctx, + struct cli_creds *client, + struct sss_sec_req **_sreq) +{ + const char *url; + struct sss_sec_req *sreq; + errno_t ret; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + url = secdb_dfl_url_create(tmp_ctx, client); + if (url == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sss_sec_new_req(tmp_ctx, sctx, url, geteuid(), &sreq); + if (ret != EOK) { + goto done; + } + + ret = EOK; + DEBUG(SSSDBG_TRACE_INTERNAL, "Created request for URL %s\n", url); + *_sreq = talloc_steal(mem_ctx, sreq); +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t key_by_uuid(TALLOC_CTX *mem_ctx, + struct sss_sec_ctx *sctx, + struct cli_creds *client, + uuid_t uuid, + char **_key) +{ + TALLOC_CTX *tmp_ctx; + errno_t ret; + char *key_match = NULL; + char **keys = NULL; + size_t nkeys; + struct sss_sec_req *sreq = NULL; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = secdb_container_url_req(tmp_ctx, sctx, client, &sreq); + if (ret != EOK) { + goto done; + } + + ret = sss_sec_list(tmp_ctx, sreq, &keys, &nkeys); + if (ret == ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, "The container was not found\n"); + goto done; + } else if (ret != EOK) { + goto done; + } + + for (size_t i = 0; i < nkeys; i++) { + if (sec_key_match_uuid(keys[i], uuid)) { + key_match = keys[i]; + break; + } + } + + if (key_match == NULL) { + DEBUG(SSSDBG_TRACE_INTERNAL, "No key matched\n"); + ret = ENOENT; + goto done; + } + + ret = EOK; + DEBUG(SSSDBG_TRACE_INTERNAL, "Found key %s\n", key_match); + *_key = talloc_steal(mem_ctx, key_match); +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t key_by_name(TALLOC_CTX *mem_ctx, + struct sss_sec_ctx *sctx, + struct cli_creds *client, + const char *name, + char **_key) +{ + TALLOC_CTX *tmp_ctx; + errno_t ret; + char *key_match = NULL; + char **keys = NULL; + size_t nkeys; + struct sss_sec_req *sreq = NULL; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = secdb_container_url_req(tmp_ctx, sctx, client, &sreq); + if (ret != EOK) { + goto done; + } + + ret = sss_sec_list(tmp_ctx, sreq, &keys, &nkeys); + if (ret == ENOENT) { + DEBUG(SSSDBG_MINOR_FAILURE, "The container was not found\n"); + goto done; + } else if (ret != EOK) { + goto done; + } + + for (size_t i = 0; i < nkeys; i++) { + if (sec_key_match_name(keys[i], name)) { + key_match = keys[i]; + break; + } + } + + if (key_match == NULL) { + DEBUG(SSSDBG_TRACE_INTERNAL, "No key matched\n"); + ret = ENOENT; + goto done; + } + + ret = EOK; + DEBUG(SSSDBG_TRACE_INTERNAL, "Found key %s\n", key_match); + *_key = talloc_steal(mem_ctx, key_match); +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t secdb_get_cc(TALLOC_CTX *mem_ctx, + struct sss_sec_ctx *sctx, + const char *secdb_key, + struct cli_creds *client, + struct kcm_ccache **_cc) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx = NULL; + struct kcm_ccache *cc = NULL; + struct sss_sec_req *sreq = NULL; + struct sss_iobuf *ccbuf; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = secdb_cc_key_req(tmp_ctx, sctx, client, secdb_key, &sreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot create secdb request [%d][%s]\n", ret, sss_strerror(ret)); + goto done; + } + + ret = sec_get(tmp_ctx, sreq, &ccbuf); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get the secret [%d][%s]\n", ret, sss_strerror(ret)); + goto done; + } + + ret = sec_kv_to_ccache_binary(tmp_ctx, secdb_key, ccbuf, client, &cc); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot convert data to ccache [%d]: %s, " + "deleting this entry\n", ret, sss_strerror(ret)); + ret = sss_sec_delete(sreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to delete entry: [%d]: %s", + ret, sss_strerror(ret)); + } + ret = ENOENT; + goto done; + } + + ret = EOK; + DEBUG(SSSDBG_TRACE_INTERNAL, "Fetched the ccache\n"); + *_cc = talloc_steal(mem_ctx, cc); +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t ccdb_secdb_init(struct kcm_ccdb *db, + struct confdb_ctx *cdb, + const char *confdb_service_path) +{ + struct ccdb_secdb *secdb = NULL; + errno_t ret; + struct sss_sec_quota *kcm_quota; + struct sss_sec_quota_opt dfl_kcm_nest_level = { + .opt_name = CONFDB_KCM_CONTAINERS_NEST_LEVEL, + .default_value = DEFAULT_SEC_CONTAINERS_NEST_LEVEL, + }; + struct sss_sec_quota_opt dfl_kcm_max_secrets = { + .opt_name = CONFDB_KCM_MAX_CCACHES, + .default_value = DEFAULT_SEC_KCM_MAX_SECRETS, + }; + struct sss_sec_quota_opt dfl_kcm_max_uid_secrets = { + .opt_name = CONFDB_KCM_MAX_UID_CCACHES, + .default_value = DEFAULT_SEC_KCM_MAX_UID_SECRETS, + }; + struct sss_sec_quota_opt dfl_kcm_max_payload_size = { + .opt_name = CONFDB_KCM_MAX_CCACHE_SIZE, + .default_value = DEFAULT_SEC_KCM_MAX_PAYLOAD_SIZE, + }; + + + secdb = talloc_zero(db, struct ccdb_secdb); + if (secdb == NULL) { + return ENOMEM; + } + + kcm_quota = talloc_zero(secdb, struct sss_sec_quota); + if (kcm_quota == NULL) { + talloc_free(secdb); + return ENOMEM; + } + + ret = sss_sec_get_quota(cdb, + confdb_service_path, + &dfl_kcm_nest_level, + &dfl_kcm_max_secrets, + &dfl_kcm_max_uid_secrets, + &dfl_kcm_max_payload_size, + kcm_quota); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to get KCM global quotas [%d]: %s\n", + ret, sss_strerror(ret)); + talloc_free(secdb); + return ret; + } + + if (kcm_quota->max_uid_secrets > 0) { + kcm_quota->max_uid_secrets += KCM_MAX_UID_EXTRA_SECRETS; + } + + ret = sss_sec_init(db, kcm_quota, &secdb->sctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot initialize the security database\n"); + talloc_free(secdb); + return ret; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "secdb initialized\n"); + db->db_handle = secdb; + return EOK; +} + +struct ccdb_secdb_nextid_state { + unsigned int nextid; +}; + +static bool is_in_use(char **keys, size_t nkeys, const char *nextid_name) +{ + for (size_t i = 0; i < nkeys; i++) { + if (sec_key_match_name(keys[i], nextid_name) == true) { + return true; + } + } + + return false; +} + +static struct tevent_req *ccdb_secdb_nextid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client) +{ + struct tevent_req *req = NULL; + struct ccdb_secdb_nextid_state *state = NULL; + struct ccdb_secdb *secdb = NULL; + const int maxtries = 3; + int numtry; + errno_t ret; + struct sss_sec_req *sreq = NULL; + char **keys = NULL; + size_t nkeys; + char *nextid_name = NULL; + + DEBUG(SSSDBG_TRACE_LIBS, "Generating a new ID\n"); + + req = tevent_req_create(mem_ctx, &state, struct ccdb_secdb_nextid_state); + if (req == NULL) { + return NULL; + } + + secdb = talloc_get_type(db->db_handle, struct ccdb_secdb); + if (secdb == NULL) { + ret = EIO; + goto immediate; + } + + ret = secdb_container_url_req(state, secdb->sctx, client, &sreq); + if (ret != EOK) { + goto immediate; + } + + ret = sss_sec_list(state, sreq, &keys, &nkeys); + if (ret == ENOENT) { + keys = NULL; + nkeys = 0; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot list keys [%d]: %s\n", + ret, sss_strerror(ret)); + goto immediate; + } + + for (numtry = 0; numtry < maxtries; numtry++) { + state->nextid = sss_rand() % MAX_CC_NUM; + nextid_name = talloc_asprintf(state, "%"SPRIuid":%u", + cli_creds_get_uid(client), + state->nextid); + if (nextid_name == NULL) { + ret = ENOMEM; + goto immediate; + } + + if (!is_in_use(keys, nkeys, nextid_name)) { + break; + } + } + + if (numtry >= maxtries) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to find a random ccache in %d tries\n", numtry); + ret = EBUSY; + goto immediate; + } + + ret = EOK; + DEBUG(SSSDBG_TRACE_LIBS, "Generated next ID %d\n", state->nextid); +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ccdb_secdb_nextid_recv(struct tevent_req *req, + unsigned int *_nextid) +{ + struct ccdb_secdb_nextid_state *state = tevent_req_data(req, + struct ccdb_secdb_nextid_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + *_nextid = state->nextid; + return EOK; +} + +static struct tevent_req *ccdb_secdb_set_default_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid) +{ + struct tevent_req *req = NULL; + struct ccdb_secdb_state *state = NULL; + struct ccdb_secdb *secdb = talloc_get_type(db->db_handle, struct ccdb_secdb); + errno_t ret; + char uuid_str[UUID_STR_SIZE]; + struct sss_sec_req *sreq = NULL; + struct sss_iobuf *iobuf; + char *cur_default; + + uuid_unparse(uuid, uuid_str); + DEBUG(SSSDBG_TRACE_INTERNAL, + "Setting the default ccache to %s\n", uuid_str); + + req = tevent_req_create(mem_ctx, &state, struct ccdb_secdb_state); + if (req == NULL) { + return NULL; + } + + ret = secdb_dfl_url_req(state, secdb->sctx, client, &sreq); + if (ret != EOK) { + goto immediate; + } + + iobuf = sss_iobuf_init_readonly(state, + (const uint8_t *) uuid_str, + UUID_STR_SIZE); + if (iobuf == NULL) { + ret = ENOMEM; + goto immediate; + } + + ret = sss_sec_get(state, sreq, (uint8_t**)&cur_default, NULL); + if (ret == ENOENT) { + ret = sec_put(state, sreq, iobuf); + } else if (ret == EOK) { + ret = sec_update(state, sreq, iobuf); + } + + if (ret != EOK) { + goto immediate; + } + + ret = EOK; + DEBUG(SSSDBG_TRACE_INTERNAL, "Set the default ccache\n"); +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ccdb_secdb_set_default_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +struct ccdb_secdb_get_default_state { + uuid_t uuid; +}; + +static struct tevent_req *ccdb_secdb_get_default_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client) +{ + struct ccdb_secdb *secdb = talloc_get_type(db->db_handle, struct ccdb_secdb); + struct tevent_req *req = NULL; + struct ccdb_secdb_get_default_state *state = NULL; + errno_t ret; + struct sss_sec_req *sreq = NULL; + struct sss_iobuf *dfl_iobuf = NULL; + size_t uuid_size; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Getting the default ccache\n"); + + req = tevent_req_create(mem_ctx, &state, struct ccdb_secdb_get_default_state); + if (req == NULL) { + return NULL; + } + + ret = secdb_dfl_url_req(state, secdb->sctx, client, &sreq); + if (ret != EOK) { + goto immediate; + } + + ret = sec_get(state, sreq, &dfl_iobuf); + if (ret == ENOENT) { + uuid_clear(state->uuid); + ret = EOK; + goto immediate; + } else if (ret != EOK) { + goto immediate; + } + + uuid_size = sss_iobuf_get_size(dfl_iobuf); + if (uuid_size != UUID_STR_SIZE) { + DEBUG(SSSDBG_OP_FAILURE, + "Unexpected UUID size %zu, deleting this entry\n", uuid_size); + ret = sss_sec_delete(sreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to delete entry: [%d]: %s, " + "consider manual removal of "SECRETS_DB_PATH"/secrets.ldb\n", + ret, sss_strerror(ret)); + sss_log(SSS_LOG_CRIT, + "Can't delete an entry from "SECRETS_DB_PATH"/secrets.ldb, " + "content seems to be corrupted. Consider file removal. " + "(Take a note, this will delete all credentials managed " + "via sssd_kcm)"); + } + uuid_clear(state->uuid); + ret = EOK; + goto immediate; + } + + uuid_parse((const char *) sss_iobuf_get_data(dfl_iobuf), state->uuid); + DEBUG(SSSDBG_TRACE_INTERNAL, "Got the default ccache\n"); + ret = EOK; +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ccdb_secdb_get_default_recv(struct tevent_req *req, + uuid_t uuid) +{ + struct ccdb_secdb_get_default_state *state = tevent_req_data(req, + struct ccdb_secdb_get_default_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + uuid_copy(uuid, state->uuid); + return EOK; +} + +static errno_t ccdb_secdb_get_cc_for_uuid(TALLOC_CTX *mem_ctx, + size_t uuid_list_count, + const char **uuid_list, + const char **uid_list, + struct ccdb_secdb *secdb, + struct kcm_ccache ***_cc_list) +{ + TALLOC_CTX *tmp_ctx; + errno_t ret; + uid_t uid; + char **list; + uuid_t uuid; + char *uuid_str; + char *secdb_key; + struct cli_creds cli_cred; + struct kcm_ccache **cc_list; + int real_count = 0; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + return ret; + } + + cc_list = talloc_zero_array(tmp_ctx, struct kcm_ccache *, uuid_list_count + 1); + if (cc_list == NULL) { + ret = ENOMEM; + goto done; + } + + for (size_t i = 0; i < uuid_list_count; i++) { + struct passwd *pwd; + + ret = split_on_separator(tmp_ctx, uuid_list[i], ':', true, true, + &list, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "split on separator failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + uuid_str = list[0]; + uuid_str[UUID_STR_SIZE - 1] = '\0'; + ret = uuid_parse(uuid_str, uuid); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "uuid parse of [%s] failed [%d]: %s\n", + list[0], ret, sss_strerror(ret)); + goto done; + } + uid = strtouint32(uid_list[i], NULL, 10); + ret = errno; + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid UID [%s] conversion to uint32 " + "[%d]: %s\n", uid_list[i], ret, + sss_strerror(ret)); + goto done; + } + + errno = 0; + pwd = getpwuid(uid); + if (pwd == NULL) { + ret = errno; + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to resolve user [%d] who " + "is the owner of an existing ccache [%d]: %s\n", + uid, ret, sss_strerror(ret)); + /* Not fatal */ + continue; + } + + cli_cred.ucred.uid = pwd->pw_uid; + cli_cred.ucred.gid = pwd->pw_gid; + + ret = key_by_uuid(tmp_ctx, secdb->sctx, &cli_cred, uuid, &secdb_key); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "key_by_uuid() failed for uuid = '%s'", uuid_str); + goto done; + } + + ret = secdb_get_cc(cc_list, secdb->sctx, secdb_key, &cli_cred, + &cc_list[real_count]); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to get ccache [%d]: %s\n", ret, sss_strerror(ret)); + /* probably ccache in old format was met and purged, just skip */ + continue; + } + + if (cc_list[real_count] == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to get cc for uuid = '%s' and uid = %s\n", + uuid_list[i], uid_list[i]); + ret = EIO; + goto done; + } + DEBUG(SSSDBG_TRACE_INTERNAL, + "Retrieved ccache [%s]\n", cc_list[real_count]->name); + real_count++; + } + + cc_list = talloc_realloc(tmp_ctx, cc_list, struct kcm_ccache *, + real_count + 1); + if (cc_list == NULL) { + ret = ENOMEM; + goto done; + } + + cc_list[real_count] = NULL; + *_cc_list = talloc_steal(mem_ctx, cc_list); + + return EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +struct ccdb_secdb_list_state { + uuid_t *uuid_list; +}; + +static errno_t ccdb_secdb_list_all_cc(TALLOC_CTX *mem_ctx, + struct krb5_ctx *krb5_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct kcm_ccache ***_cc_list) +{ + struct ccdb_secdb *secdb = talloc_get_type(db->db_handle, struct ccdb_secdb); + TALLOC_CTX *tmp_ctx; + errno_t ret; + const char **uid_list; + const char **uuid_list; + size_t uuid_list_count; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Retrieving all ccaches\n"); + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + return ret; + } + + ret = sss_sec_list_cc_uuids(tmp_ctx, secdb->sctx, &uuid_list, &uid_list, &uuid_list_count); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Error retrieving ccache uuid list " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } else if (ret == ENOENT) { + goto done; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Found [%zu] ccache uuids\n", uuid_list_count); + + /* New count is full cc list size minus getpwuid() failures */ + ret = ccdb_secdb_get_cc_for_uuid(mem_ctx, uuid_list_count, uuid_list, + uid_list, secdb, _cc_list); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Error getting cc list from uuid list " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Retrieving all caches done\n"); + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static struct tevent_req *ccdb_secdb_list_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client) +{ + struct ccdb_secdb *secdb = talloc_get_type(db->db_handle, struct ccdb_secdb); + struct tevent_req *req = NULL; + struct ccdb_secdb_list_state *state = NULL; + errno_t ret; + char **keys = NULL; + size_t nkeys; + struct sss_sec_req *sreq = NULL; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Listing all ccaches\n"); + + req = tevent_req_create(mem_ctx, &state, struct ccdb_secdb_list_state); + if (req == NULL) { + return NULL; + } + + ret = secdb_container_url_req(state, secdb->sctx, client, &sreq); + if (ret != EOK) { + goto immediate; + } + + ret = sss_sec_list(state, sreq, &keys, &nkeys); + if (ret == ENOENT) { + nkeys = 0; + /* Fall through and return an empty list */ + } else if (ret != EOK) { + goto immediate; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "Found %zu ccaches\n", nkeys); + + state->uuid_list = talloc_array(state, uuid_t, nkeys + 1); + if (state->uuid_list == NULL) { + ret = ENOMEM; + goto immediate; + } + + for (size_t i = 0; i < nkeys; i++) { + ret = sec_key_get_uuid(keys[i], + state->uuid_list[i]); + if (ret != EOK) { + goto immediate; + } + } + /* Sentinel */ + uuid_clear(state->uuid_list[nkeys]); + + DEBUG(SSSDBG_TRACE_INTERNAL, "Listing all caches done\n"); + ret = EOK; +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ccdb_secdb_list_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uuid_t **_uuid_list) +{ + struct ccdb_secdb_list_state *state = tevent_req_data(req, + struct ccdb_secdb_list_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + *_uuid_list = talloc_steal(mem_ctx, state->uuid_list); + return EOK; +} + +struct ccdb_secdb_getbyuuid_state { + struct kcm_ccache *cc; +}; + +static struct tevent_req *ccdb_secdb_getbyuuid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid) +{ + struct ccdb_secdb *secdb = talloc_get_type(db->db_handle, struct ccdb_secdb); + struct tevent_req *req = NULL; + struct ccdb_secdb_getbyuuid_state *state = NULL; + errno_t ret; + char *secdb_key = NULL; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Getting ccache by UUID\n"); + + req = tevent_req_create(mem_ctx, &state, struct ccdb_secdb_getbyuuid_state); + if (req == NULL) { + return NULL; + } + + ret = key_by_uuid(state, secdb->sctx, client, uuid, &secdb_key); + if (ret == ENOENT) { + state->cc = NULL; + ret = EOK; + goto immediate; + } else if (ret != EOK) { + goto immediate; + } + + ret = secdb_get_cc(state, secdb->sctx, secdb_key, client, &state->cc); + if (ret == ENOENT) { + state->cc = NULL; + ret = EOK; + goto immediate; + } else if (ret != EOK) { + goto immediate; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Got ccache by UUID\n"); + ret = EOK; +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ccdb_secdb_getbyuuid_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct kcm_ccache **_cc) +{ + struct ccdb_secdb_getbyuuid_state *state = tevent_req_data(req, + struct ccdb_secdb_getbyuuid_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + *_cc = talloc_steal(mem_ctx, state->cc); + return EOK; +} + +struct ccdb_secdb_getbyname_state { + struct kcm_ccache *cc; +}; + +static struct tevent_req *ccdb_secdb_getbyname_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + const char *name) +{ + struct ccdb_secdb *secdb = talloc_get_type(db->db_handle, struct ccdb_secdb); + struct tevent_req *req = NULL; + struct ccdb_secdb_getbyname_state *state = NULL; + errno_t ret; + char *secdb_key = NULL; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Getting ccache by name\n"); + + req = tevent_req_create(mem_ctx, &state, struct ccdb_secdb_getbyname_state); + if (req == NULL) { + return NULL; + } + + ret = key_by_name(state, secdb->sctx, client, name, &secdb_key); + if (ret == ENOENT) { + state->cc = NULL; + ret = EOK; + goto immediate; + } else if (ret != EOK) { + goto immediate; + } + + ret = secdb_get_cc(state, secdb->sctx, secdb_key, client, &state->cc); + if (ret == ENOENT) { + state->cc = NULL; + ret = EOK; + goto immediate; + } else if (ret != EOK) { + goto immediate; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Got ccache by name\n"); + ret = EOK; +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ccdb_secdb_getbyname_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct kcm_ccache **_cc) +{ + struct ccdb_secdb_getbyname_state *state = tevent_req_data(req, + struct ccdb_secdb_getbyname_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + *_cc = talloc_steal(mem_ctx, state->cc); + return EOK; +} + + +struct ccdb_secdb_name_by_uuid_state { + const char *name; +}; + +struct tevent_req *ccdb_secdb_name_by_uuid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid) +{ + struct ccdb_secdb *secdb = talloc_get_type(db->db_handle, struct ccdb_secdb); + struct tevent_req *req = NULL; + struct ccdb_secdb_name_by_uuid_state *state = NULL; + errno_t ret; + char *key; + const char *name; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Translating UUID to name\n"); + + req = tevent_req_create(mem_ctx, &state, struct ccdb_secdb_name_by_uuid_state); + if (req == NULL) { + return NULL; + } + + ret = key_by_uuid(state, secdb->sctx, client, uuid, &key); + if (ret == ENOENT) { + ret = ERR_NO_CREDS; + goto immediate; + } else if (ret != EOK) { + goto immediate; + } + + name = sec_key_get_name(key); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Malformed key, cannot get name\n"); + goto immediate; + } + + state->name = talloc_strdup(state, name); + if (state->name == NULL) { + ret = ENOMEM; + goto immediate; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Got ccache by UUID\n"); + ret = EOK; +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +errno_t ccdb_secdb_name_by_uuid_recv(struct tevent_req *req, + TALLOC_CTX *sec_ctx, + const char **_name) +{ + struct ccdb_secdb_name_by_uuid_state *state = tevent_req_data(req, + struct ccdb_secdb_name_by_uuid_state); + TEVENT_REQ_RETURN_ON_ERROR(req); + *_name = talloc_steal(sec_ctx, state->name); + return EOK; +} + +struct ccdb_secdb_uuid_by_name_state { + uuid_t uuid; +}; + +struct tevent_req *ccdb_secdb_uuid_by_name_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + const char *name) +{ + struct ccdb_secdb *secdb = talloc_get_type(db->db_handle, struct ccdb_secdb); + struct tevent_req *req = NULL; + struct ccdb_secdb_uuid_by_name_state *state = NULL; + errno_t ret; + char *key; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Translating name to UUID\n"); + + req = tevent_req_create(mem_ctx, &state, struct ccdb_secdb_uuid_by_name_state); + if (req == NULL) { + return NULL; + } + + ret = key_by_name(state, secdb->sctx, client, name, &key); + if (ret == ENOENT) { + ret = ERR_NO_CREDS; + goto immediate; + } else if (ret != EOK) { + goto immediate; + } + + ret = sec_key_get_uuid(key, state->uuid); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Malformed key, cannot get UUID\n"); + goto immediate; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Got ccache by UUID\n"); + ret = EOK; +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ccdb_secdb_uuid_by_name_recv(struct tevent_req *req, + TALLOC_CTX *sec_ctx, + uuid_t _uuid) +{ + struct ccdb_secdb_uuid_by_name_state *state = tevent_req_data(req, + struct ccdb_secdb_uuid_by_name_state); + TEVENT_REQ_RETURN_ON_ERROR(req); + uuid_copy(_uuid, state->uuid); + return EOK; +} + + +static struct tevent_req *ccdb_secdb_create_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + struct kcm_ccache *cc) +{ + struct ccdb_secdb *secdb = talloc_get_type(db->db_handle, struct ccdb_secdb); + struct tevent_req *req = NULL; + struct ccdb_secdb_state *state = NULL; + errno_t ret; + struct sss_sec_req *container_req = NULL; + struct sss_sec_req *ccache_req = NULL; + const char *url; + struct sss_iobuf *ccache_payload; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Creating ccache storage for %s\n", cc->name); + + req = tevent_req_create(mem_ctx, &state, struct ccdb_secdb_state); + if (req == NULL) { + return NULL; + } + + /* Do the encoding asap so that if we fail, we don't even attempt any + * writes */ + ret = kcm_ccache_to_secdb_kv(state, cc, client, &url, &ccache_payload); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot convert cache %s to JSON [%d]: %s\n", + cc->name, ret, sss_strerror(ret)); + goto immediate; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Creating the ccache container\n"); + ret = secdb_container_url_req(state, secdb->sctx, client, &container_req); + if (ret != EOK) { + goto immediate; + } + + ret = sss_sec_create_container(container_req); + if (ret == EEXIST) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Container already exists, ignoring\n"); + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create the ccache container\n"); + goto immediate; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "ccache container created\n"); + DEBUG(SSSDBG_TRACE_INTERNAL, "creating empty ccache payload\n"); + + ret = secdb_cc_url_req(state, secdb->sctx, client, url, &ccache_req); + if (ret != EOK) { + goto immediate; + } + + ret = sec_put(state, ccache_req, ccache_payload); + if (ret != EOK) { + goto immediate; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "payload created\n"); + ret = EOK; +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ccdb_secdb_create_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +static struct tevent_req *ccdb_secdb_mod_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid, + struct kcm_mod_ctx *mod_cc) +{ + struct ccdb_secdb *secdb = talloc_get_type(db->db_handle, struct ccdb_secdb); + struct tevent_req *req = NULL; + struct ccdb_secdb_state *state = NULL; + errno_t ret; + char *secdb_key = NULL; + struct kcm_ccache *cc = NULL; + struct sss_iobuf *payload = NULL; + struct sss_sec_req *sreq = NULL; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Modifying ccache\n"); + + req = tevent_req_create(mem_ctx, &state, struct ccdb_secdb_state); + if (req == NULL) { + return NULL; + } + + ret = key_by_uuid(state, secdb->sctx, client, uuid, &secdb_key); + if (ret == ENOENT) { + ret = ERR_NO_CREDS; + goto immediate; + } else if (ret != EOK) { + goto immediate; + } + + ret = secdb_get_cc(state, secdb->sctx, secdb_key, client, &cc); + if (ret == ENOENT) { + ret = ERR_NO_CREDS; + goto immediate; + } else if (ret != EOK) { + goto immediate; + } + + ret = kcm_mod_cc(cc, mod_cc); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot modify ccache [%d]: %s\n", + ret, sss_strerror(ret)); + goto immediate; + } + + ret = kcm_ccache_to_sec_input_binary(state, cc, &payload); + if (ret != EOK) { + goto immediate; + } + + ret = secdb_cc_key_req(state, secdb->sctx, client, secdb_key, &sreq); + if (ret != EOK) { + goto immediate; + } + + ret = sec_update(state, sreq, payload); + if (ret != EOK) { + goto immediate; + } + + ret = EOK; +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ccdb_secdb_mod_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +static struct tevent_req *ccdb_secdb_store_cred_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid, + struct sss_iobuf *cred_blob) +{ + struct ccdb_secdb *secdb = talloc_get_type(db->db_handle, struct ccdb_secdb); + struct tevent_req *req = NULL; + struct ccdb_secdb_state *state = NULL; + char *secdb_key = NULL; + struct kcm_ccache *cc = NULL; + struct sss_iobuf *payload = NULL; + struct sss_sec_req *sreq = NULL; + errno_t ret; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Storing creds in ccache\n"); + + req = tevent_req_create(mem_ctx, &state, struct ccdb_secdb_state); + if (req == NULL) { + return NULL; + } + + ret = key_by_uuid(state, secdb->sctx, client, uuid, &secdb_key); + if (ret == ENOENT) { + ret = ERR_NO_CREDS; + goto immediate; + } else if (ret != EOK) { + goto immediate; + } + + ret = secdb_get_cc(state, secdb->sctx, secdb_key, client, &cc); + if (ret == ENOENT) { + ret = ERR_NO_CREDS; + goto immediate; + } else if (ret != EOK) { + goto immediate; + } + + ret = kcm_cc_store_cred_blob(cc, cred_blob); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot store credentials to ccache [%d]: %s\n", + ret, sss_strerror(ret)); + goto immediate; + } + + ret = kcm_ccache_to_sec_input_binary(state, cc, &payload); + if (ret != EOK) { + goto immediate; + } + + ret = secdb_cc_key_req(state, secdb->sctx, client, secdb_key, &sreq); + if (ret != EOK) { + goto immediate; + } + + ret = sec_update(state, sreq, payload); + if (ret != EOK) { + goto immediate; + } + + ret = EOK; +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ccdb_secdb_store_cred_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +static struct tevent_req *ccdb_secdb_delete_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ccdb *db, + struct cli_creds *client, + uuid_t uuid) +{ + struct tevent_req *req = NULL; + struct ccdb_secdb_state *state = NULL; + struct ccdb_secdb *secdb = talloc_get_type(db->db_handle, struct ccdb_secdb); + struct sss_sec_req *container_req = NULL; + struct sss_sec_req *sreq = NULL; + char *secdb_key = NULL; + char **keys = NULL; + size_t nkeys; + errno_t ret; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Deleting ccache\n"); + + req = tevent_req_create(mem_ctx, &state, struct ccdb_secdb_state); + if (req == NULL) { + return NULL; + } + + ret = secdb_container_url_req(state, secdb->sctx, client, &container_req); + if (ret != EOK) { + goto immediate; + } + + ret = sss_sec_list(state, container_req, &keys, &nkeys); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "No ccaches to delete\n"); + goto immediate; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "Found %zu ccaches\n", nkeys); + + if (nkeys == 0) { + ret = EOK; + goto immediate; + } + + ret = key_by_uuid(state, secdb->sctx, client, uuid, &secdb_key); + if (ret == ENOENT) { + ret = ERR_NO_CREDS; + goto immediate; + } else if (ret != EOK) { + goto immediate; + } + + ret = secdb_cc_key_req(state, secdb->sctx, client, secdb_key, &sreq); + if (ret != EOK) { + goto immediate; + } + + ret = sss_sec_delete(sreq); + if (ret != EOK) { + goto immediate; + } + + if (nkeys > 1) { + DEBUG(SSSDBG_TRACE_INTERNAL, "There are other ccaches, done\n"); + ret = EOK; + goto immediate; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "Removing ccache container\n"); + + ret = sss_sec_delete(container_req); + if (ret != EOK) { + goto immediate; + } + + ret = EOK; +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t ccdb_secdb_delete_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} + +const struct kcm_ccdb_ops ccdb_secdb_ops = { + .init = ccdb_secdb_init, + + .nextid_send = ccdb_secdb_nextid_send, + .nextid_recv = ccdb_secdb_nextid_recv, + + .set_default_send = ccdb_secdb_set_default_send, + .set_default_recv = ccdb_secdb_set_default_recv, + + .get_default_send = ccdb_secdb_get_default_send, + .get_default_recv = ccdb_secdb_get_default_recv, + + .list_send = ccdb_secdb_list_send, + .list_recv = ccdb_secdb_list_recv, + + .list_all_cc = ccdb_secdb_list_all_cc, + + .getbyname_send = ccdb_secdb_getbyname_send, + .getbyname_recv = ccdb_secdb_getbyname_recv, + + .getbyuuid_send = ccdb_secdb_getbyuuid_send, + .getbyuuid_recv = ccdb_secdb_getbyuuid_recv, + + .name_by_uuid_send = ccdb_secdb_name_by_uuid_send, + .name_by_uuid_recv = ccdb_secdb_name_by_uuid_recv, + + .uuid_by_name_send = ccdb_secdb_uuid_by_name_send, + .uuid_by_name_recv = ccdb_secdb_uuid_by_name_recv, + + .create_send = ccdb_secdb_create_send, + .create_recv = ccdb_secdb_create_recv, + + .mod_send = ccdb_secdb_mod_send, + .mod_recv = ccdb_secdb_mod_recv, + + .store_cred_send = ccdb_secdb_store_cred_send, + .store_cred_recv = ccdb_secdb_store_cred_recv, + + .delete_send = ccdb_secdb_delete_send, + .delete_recv = ccdb_secdb_delete_recv, +}; diff --git a/src/responder/kcm/kcmsrv_cmd.c b/src/responder/kcm/kcmsrv_cmd.c new file mode 100644 index 0000000..9c37e3c --- /dev/null +++ b/src/responder/kcm/kcmsrv_cmd.c @@ -0,0 +1,667 @@ +/* + SSSD + + KCM Server - the KCM server request and reply parsing and dispatching + + Copyright (C) Red Hat, 2016 + + 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 <sys/uio.h> +#include <krb5/krb5.h> + +#include "config.h" +#include "util/util.h" +#include "responder/common/responder.h" +#include "responder/kcm/kcmsrv_pvt.h" +#include "responder/kcm/kcmsrv_ops.h" + +/* The first four bytes of a message is always the size */ +#define KCM_MSG_LEN_SIZE 4 + +/* The return code is 32bits */ +#define KCM_RETCODE_SIZE 4 + +/* KCM operation, its raw input and raw output and result */ +struct kcm_op_io { + struct kcm_op *op; + struct kcm_data request; + struct sss_iobuf *reply; +}; + +/** + * KCM IO-vector operations + */ +struct kcm_iovec { + /* We don't use iovec b/c void pointers don't allow for + * pointer arithmetics and it's convenient to keep track + * of processed bytes + */ + uint8_t *kiov_base; + size_t kiov_len; + size_t nprocessed; +}; + +static errno_t kcm_iovec_op(int fd, struct kcm_iovec *kiov, bool do_read) +{ + ssize_t len; + struct iovec iov[1]; + + iov[0].iov_base = kiov->kiov_base + kiov->nprocessed; + iov[0].iov_len = kiov->kiov_len - kiov->nprocessed; + if (iov[0].iov_len == 0) { + /* This iovec is full (read) or depleted (write), proceed to the next one */ + return EOK; + } + + if (do_read) { + len = readv(fd, iov, 1); + } else { + len = writev(fd, iov, 1); + } + + if (len == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { + return EAGAIN; + } else { + return errno; + } + } + + if (len == 0) { + /* Read event on fd that doesn't yield data? error */ + return ENODATA; + } + + /* Decrease the amount of available free space in the iovec */ + kiov->nprocessed += len; + return EOK; +} + +static errno_t kcm_read_iovec(int fd, struct kcm_iovec *kiov) +{ + return kcm_iovec_op(fd, kiov, true); +} + +static errno_t kcm_write_iovec(int fd, struct kcm_iovec *kiov) +{ + return kcm_iovec_op(fd, kiov, false); +} + +/** + * Parsing KCM input + * + * The request is received as two IO vectors: + * + * first iovec: + * length 32-bit big-endian integer + * + * second iovec: + * major protocol number 8-bit big-endian integer + * minor protocol number 8-bit big-endian integer + * opcode 16-bit big-endian integer + * message payload buffer + */ +struct kcm_reqbuf { + uint8_t lenbuf[KCM_MSG_LEN_SIZE]; + struct kcm_iovec v_len; + + /* Includes the major, minor versions etc */ + struct kcm_iovec v_msg; +}; + +static uint32_t kcm_input_get_payload_len(struct kcm_iovec *v) +{ + size_t lc = 0; + uint32_t len_be = 0; + + /* The first 4 bytes before the payload is message length */ + SAFEALIGN_COPY_UINT32_CHECK(&len_be, v->kiov_base, v->kiov_len, &lc); + + return be32toh(len_be); +} + +static errno_t kcm_input_parse(struct kcm_reqbuf *reqbuf, + struct kcm_op_io *op_io) +{ + size_t mc = 0; + uint16_t opcode_be = 0; + uint32_t msglen; + uint8_t proto_maj = 0; + uint8_t proto_min = 0; + + msglen = kcm_input_get_payload_len(&reqbuf->v_len); + DEBUG(SSSDBG_TRACE_LIBS, + "Received message with length %"PRIu32"\n", msglen); + + if (msglen == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Illegal zero-length message\n"); + return EBADMSG; + } + + if (msglen != reqbuf->v_msg.nprocessed) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Sender claims the message is %"PRIu32" bytes, " + "but received %zu\n", + msglen, reqbuf->v_msg.nprocessed); + return EBADMSG; + } + + /* First 16 bits are 8 bit major and 8bit minor protocol version */ + SAFEALIGN_COPY_UINT8_CHECK(&proto_maj, + reqbuf->v_msg.kiov_base + mc, + reqbuf->v_msg.kiov_len, + &mc); + SAFEALIGN_COPY_UINT8_CHECK(&proto_min, + reqbuf->v_msg.kiov_base + mc, + reqbuf->v_msg.kiov_len, + &mc); + + if (proto_maj != KCM_PROTOCOL_VERSION_MAJOR) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Expected major version %d, got %"PRIu16"\n", + KCM_PROTOCOL_VERSION_MAJOR, (uint16_t) proto_maj); + return ERR_KCM_MALFORMED_IN_PKT; + } + + if (proto_min != KCM_PROTOCOL_VERSION_MINOR) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Expected minor version %d, got %"PRIu16"\n", + KCM_PROTOCOL_VERSION_MINOR, (uint16_t) proto_maj); + return ERR_KCM_MALFORMED_IN_PKT; + } + + SAFEALIGN_COPY_UINT16_CHECK(&opcode_be, + reqbuf->v_msg.kiov_base + mc, + reqbuf->v_msg.kiov_len, + &mc); + + op_io->op = kcm_get_opt(be16toh(opcode_be)); + if (op_io->op == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Did not find a KCM operation handler for the requested opcode\n"); + return ERR_KCM_OP_NOT_IMPLEMENTED; + } + + /* The operation only receives the payload, not the opcode or the protocol info */ + op_io->request.data = reqbuf->v_msg.kiov_base + mc; + op_io->request.length = reqbuf->v_msg.nprocessed - mc; + + return EOK; +} + +/** + * Constructing a reply for failure and success + * + * The reply consists of three IO vectors: + * 1) length iovec: + * length: 32-bit big-endian + * + * 2) return code iovec: + * retcode: 32-bit big-endian. Non-zero on failure in the KCM server, + * zero if the KCM operation ran (even if the operation itself + * failed) + * + * 3) reply iovec + * message: buffer, first 32-bits of the buffer is the return code of + * the KCM operation, the rest depends on the operation itself. + * The buffer's length is specified by the first integer in the + * reply (very intuitive, right?) + * + * The client always reads the length and return code iovectors. However, the + * client reads the reply iovec only if retcode is 0 in the return code iovector + * (see kcmio_unix_socket_read() in the MIT tree) + */ +struct kcm_repbuf { + uint8_t lenbuf[KCM_MSG_LEN_SIZE]; + struct kcm_iovec v_len; + + uint8_t rcbuf[KCM_RETCODE_SIZE]; + struct kcm_iovec v_rc; + + struct kcm_iovec v_msg; +}; + +static errno_t kcm_failbuf_construct(errno_t ret, + struct kcm_repbuf *repbuf) +{ + size_t c; + + c = 0; + SAFEALIGN_SETMEM_UINT32(repbuf->lenbuf, 0, &c); + c = 0; + SAFEALIGN_SETMEM_UINT32(repbuf->rcbuf, htobe32(ret), &c); + + DEBUG(SSSDBG_TRACE_LIBS, "Sent reply with error %d\n", ret); + return EOK; +} + +/* retcode is 0 if the operation at least ran, non-zero if there + * was some kind of internal KCM error, like input couldn't be parsed + */ +static errno_t kcm_output_construct(TALLOC_CTX *mem_ctx, + struct kcm_op_io *op_io, + struct kcm_repbuf *repbuf) +{ + uint8_t *rep; + size_t replen; + size_t c; + + replen = sss_iobuf_get_len(op_io->reply); + if (replen > KCM_PACKET_MAX_SIZE) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Reply exceeds the KCM protocol limit, aborting\n"); + return E2BIG; + } + + DEBUG(SSSDBG_TRACE_LIBS, + "Sending a reply with %zu bytes of payload\n", replen); + c = 0; + SAFEALIGN_SETMEM_UINT32(repbuf->lenbuf, htobe32(replen), &c); + + c = 0; + SAFEALIGN_SETMEM_UINT32(repbuf->rcbuf, 0, &c); + + if (replen > 0) { + rep = talloc_array(mem_ctx, uint8_t, replen); + if (rep == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to allocate memory for the message\n"); + return ENOMEM; + } + + c = 0; + SAFEALIGN_MEMCPY_CHECK(rep, + sss_iobuf_get_data(op_io->reply), + replen, + replen, + &c); + + /* Set the buffer and its length to send to KCM client */ + repbuf->v_msg.kiov_base = rep; + repbuf->v_msg.kiov_len = replen; + } + + return EOK; +} + +/** + * Construct a reply buffer and send it to the KCM client + */ +static void kcm_reply_error(struct cli_ctx *cctx, + errno_t retcode, + struct kcm_repbuf *repbuf) +{ + errno_t ret; + krb5_error_code kerr; + + DEBUG(retcode == ERR_KCM_OP_NOT_IMPLEMENTED ? + SSSDBG_MINOR_FAILURE : SSSDBG_OP_FAILURE, + "KCM operation returns failure [%d]: %s\n", + retcode, sss_strerror(retcode)); + kerr = sss2krb5_error(retcode); + + ret = kcm_failbuf_construct(kerr, repbuf); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot construct the reply buffer, terminating client\n"); + talloc_free(cctx); + return; + } + + TEVENT_FD_WRITEABLE(cctx->cfde); +} + +/** + * Request-reply dispatcher + */ +struct kcm_req_ctx { + /* client context owns per-client buffers including this one */ + struct cli_ctx *cctx; + + /* raw IO buffers */ + struct kcm_reqbuf reqbuf; + struct kcm_repbuf repbuf; + + /* long-lived responder structures */ + struct kcm_ctx *kctx; + + struct kcm_op_io op_io; +}; + +static void kcm_send_reply(struct kcm_req_ctx *req_ctx) +{ + struct cli_ctx *cctx; + errno_t ret; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Sending a reply\n"); + + cctx = req_ctx->cctx; + + ret = kcm_output_construct(req_ctx, &req_ctx->op_io, &req_ctx->repbuf); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot construct the reply buffer, terminating client\n"); + kcm_reply_error(cctx, ret, &req_ctx->repbuf); + return; + } + + TEVENT_FD_WRITEABLE(cctx->cfde); +} + +static void kcm_cmd_request_done(struct tevent_req *req); + +static errno_t kcm_cmd_dispatch(struct kcm_ctx *kctx, + struct kcm_req_ctx *req_ctx) +{ + struct tevent_req *req; + struct cli_ctx *cctx; + struct kcm_conn_data *conn_data; + + cctx = req_ctx->cctx; + conn_data = talloc_get_type(cctx->state_ctx, struct kcm_conn_data); + + req = kcm_cmd_send(req_ctx, + cctx->ev, + kctx->qctx, + req_ctx->kctx->kcm_data, + conn_data, + req_ctx->cctx->creds, + &req_ctx->op_io.request, + req_ctx->op_io.op); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to schedule KCM operation.\n"); + return ENOMEM; + } + + tevent_req_set_callback(req, kcm_cmd_request_done, req_ctx); + return EOK; +} + +static void kcm_cmd_request_done(struct tevent_req *req) +{ + struct kcm_req_ctx *req_ctx; + errno_t ret; + + req_ctx = tevent_req_callback_data(req, struct kcm_req_ctx); + + ret = kcm_cmd_recv(req_ctx, req, + &req_ctx->op_io.reply); + talloc_free(req); + if (ret != EOK) { + if (ret == ERR_KCM_OP_NOT_IMPLEMENTED) { + DEBUG(SSSDBG_MINOR_FAILURE, "%s\n", sss_strerror(ret)); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "KCM operation failed [%d]: %s\n", ret, sss_strerror(ret)); + } + kcm_reply_error(req_ctx->cctx, ret, &req_ctx->repbuf); + return; + } + + kcm_send_reply(req_ctx); +} + +static errno_t kcm_recv_data(TALLOC_CTX *mem_ctx, + int fd, + struct kcm_reqbuf *reqbuf) +{ + uint8_t *msg; + uint32_t msglen; + errno_t ret; + + ret = kcm_read_iovec(fd, &reqbuf->v_len); + if (ret != EOK) { + /* Not all errors are fatal, hence we don't print DEBUG messages + * here, but in the caller + */ + return ret; + } + + msglen = kcm_input_get_payload_len(&reqbuf->v_len); + if (msglen > KCM_PACKET_MAX_SIZE) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Request exceeds the KCM protocol limit, aborting\n"); + return E2BIG; + } + + msg = talloc_array(mem_ctx, uint8_t, msglen); + if (msg == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to allocate memory for the message\n"); + return ENOMEM; + } + + /* Set the buffer and its expected len to receive the data */ + reqbuf->v_msg.kiov_base = msg; + reqbuf->v_msg.kiov_len = msglen; + + ret = kcm_read_iovec(fd, &reqbuf->v_msg); + if (ret != EOK) { + /* Not all errors are fatal, hence we don't print DEBUG messages + * here, but in the caller + */ + return ret; + } + + return EOK; +} + +/* Mind that kcm_new_req() does not take a mem_ctx argument on purpose as we + * really want the cctx to be the memory context here so that if the client + * disconnects, the request goes away. */ +static struct kcm_req_ctx *kcm_new_req(struct cli_ctx *cctx, + struct kcm_ctx *kctx) +{ + struct kcm_req_ctx *req; + + req = talloc_zero(cctx, struct kcm_req_ctx); + if (req == NULL) { + return NULL; + } + + req->reqbuf.v_len.kiov_base = req->reqbuf.lenbuf; + req->reqbuf.v_len.kiov_len = KCM_MSG_LEN_SIZE; + + req->repbuf.v_len.kiov_base = req->repbuf.lenbuf; + req->repbuf.v_len.kiov_len = KCM_MSG_LEN_SIZE; + + req->repbuf.v_rc.kiov_base = req->repbuf.rcbuf; + req->repbuf.v_rc.kiov_len = KCM_RETCODE_SIZE; + + req->cctx = cctx; + req->kctx = kctx; + + return req; +} + +static void kcm_recv(struct cli_ctx *cctx) +{ + struct kcm_req_ctx *req; + struct kcm_ctx *kctx; + int ret; + + kctx = talloc_get_type(cctx->rctx->pvt_ctx, struct kcm_ctx); + req = talloc_get_type(cctx->protocol_ctx, struct kcm_req_ctx); + if (req == NULL) { + /* A new request comes in, setup data structures. */ + req = kcm_new_req(cctx, kctx); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot set up client connection\n"); + talloc_free(cctx); + return; + } + + cctx->protocol_ctx = req; + } + + /* Shared data between requests that originates in the same connection. */ + if (cctx->state_ctx == NULL) { + cctx->state_ctx = talloc_zero(cctx, struct kcm_conn_data); + if (cctx->state_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot set up client state\n"); + talloc_free(cctx); + return; + } + } + + ret = kcm_recv_data(req, cctx->cfd, &req->reqbuf); + switch (ret) { + case ENODATA: + DEBUG(SSSDBG_TRACE_ALL, "Client closed connection.\n"); + talloc_free(cctx); + return; + case EAGAIN: + DEBUG(SSSDBG_TRACE_ALL, "Retry later\n"); + return; + case EOK: + /* all fine */ + break; + default: + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to receive data (%d, %s), aborting client\n", + ret, sss_strerror(ret)); + talloc_free(cctx); + return; + } + + ret = kcm_input_parse(&req->reqbuf, &req->op_io); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to parse data (%d, %s), aborting client\n", + ret, sss_strerror(ret)); + talloc_free(cctx); + return; + } + + /* do not read anymore, client is done sending */ + TEVENT_FD_NOT_READABLE(cctx->cfde); + + ret = kcm_cmd_dispatch(kctx, req); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to dispatch KCM operation [%d]: %s\n", + ret, sss_strerror(ret)); + /* Fail with reply */ + kcm_reply_error(cctx, ret, &req->repbuf); + return; + } + + /* Dispatched request resumes in kcm_cmd_request_done */ + return; +} + +static int kcm_send_data(struct cli_ctx *cctx) +{ + struct kcm_req_ctx *req; + errno_t ret; + + req = talloc_get_type(cctx->protocol_ctx, struct kcm_req_ctx); + + ret = kcm_write_iovec(cctx->cfd, &req->repbuf.v_len); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to write the length iovec [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + ret = kcm_write_iovec(cctx->cfd, &req->repbuf.v_rc); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to write the retcode iovec [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + ret = kcm_write_iovec(cctx->cfd, &req->repbuf.v_msg); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to write the msg iovec [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + return EOK; +} + +static void kcm_send(struct cli_ctx *cctx) +{ + errno_t ret; + + ret = kcm_send_data(cctx); + if (ret == EAGAIN) { + DEBUG(SSSDBG_TRACE_ALL, "Sending data again..\n"); + return; + } else if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to send data, aborting client!\n"); + talloc_free(cctx); + return; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "All data sent!\n"); + TEVENT_FD_NOT_WRITEABLE(cctx->cfde); + TEVENT_FD_READABLE(cctx->cfde); + talloc_zfree(cctx->protocol_ctx); + return; +} + +static void kcm_fd_handler(struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, void *ptr) +{ + sss_client_fd_handler(ptr, kcm_recv, kcm_send, flags); +} + +int kcm_connection_setup(struct cli_ctx *cctx) +{ + cctx->cfd_handler = kcm_fd_handler; + return EOK; +} + +krb5_error_code sss2krb5_error(errno_t err) +{ + switch (err) { + case EOK: + return 0; + case ENOMEM: + return KRB5_CC_NOMEM; + case EACCES: + return KRB5_FCC_PERM; + case ERR_KCM_OP_NOT_IMPLEMENTED: + return KRB5_FCC_INTERNAL; + case ERR_WRONG_NAME_FORMAT: + return KRB5_CC_BADNAME; + case ERR_NO_MATCHING_CREDS: + return KRB5_FCC_NOFILE; + case ERR_NO_CREDS: + return KRB5_CC_NOTFOUND; + case ERR_KCM_CC_END: + return KRB5_CC_END; + case ERR_KCM_MALFORMED_IN_PKT: + case EINVAL: + case EIO: + return KRB5_CC_IO; + } + + return KRB5_FCC_INTERNAL; +} + +/* Dummy, not used here but required to link to other responder files */ +struct cli_protocol_version *register_cli_protocol_version(void) +{ + return NULL; +} diff --git a/src/responder/kcm/kcmsrv_op_queue.c b/src/responder/kcm/kcmsrv_op_queue.c new file mode 100644 index 0000000..29af521 --- /dev/null +++ b/src/responder/kcm/kcmsrv_op_queue.c @@ -0,0 +1,332 @@ +/* + SSSD + + KCM Server - the KCM operations wait queue + + Copyright (C) Red Hat, 2017 + + 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 "util/util.h" +#include "util/util_creds.h" +#include "responder/kcm/kcmsrv_pvt.h" + +struct kcm_ops_queue_entry { + struct tevent_req *req; + + struct kcm_ops_queue *queue; + + struct kcm_ops_queue_entry *next; + struct kcm_ops_queue_entry *prev; +}; + +struct kcm_ops_queue { + uid_t uid; + struct tevent_context *ev; + struct kcm_ops_queue_ctx *qctx; + + struct kcm_ops_queue_entry *head; +}; + +struct kcm_ops_queue_ctx { + struct kcm_ctx *kctx; + + /* UID:kcm_ops_queue */ + hash_table_t *wait_queue_hash; +}; + +/* + * Per-UID wait queue + * + * They key in the hash table is the UID of the peer. The value of each + * hash table entry is kcm_ops_queue structure which in turn contains a + * linked list of kcm_ops_queue_entry structures * which primarily hold the + * tevent request being queued. + */ +struct kcm_ops_queue_ctx *kcm_ops_queue_create(TALLOC_CTX *mem_ctx, + struct kcm_ctx *kctx) +{ + errno_t ret; + struct kcm_ops_queue_ctx *queue_ctx; + + queue_ctx = talloc_zero(mem_ctx, struct kcm_ops_queue_ctx); + if (queue_ctx == NULL) { + return NULL; + } + + ret = sss_hash_create_ex(mem_ctx, 0, + &queue_ctx->wait_queue_hash, 0, 0, 0, 0, + NULL, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_hash_create failed [%d]: %s\n", ret, sss_strerror(ret)); + talloc_free(queue_ctx); + return NULL; + } + + queue_ctx->kctx = kctx; + + return queue_ctx; +} + +void queue_removal_cb(struct tevent_context *ctx, + struct tevent_immediate *imm, + void *private_data) +{ + struct kcm_ops_queue *kq = talloc_get_type(private_data, + struct kcm_ops_queue); + int ret; + hash_key_t key; + + talloc_free(imm); + + if (kq->head != NULL) { + DEBUG(SSSDBG_TRACE_LIBS, "The queue is no longer empty\n"); + return; + } + + key.type = HASH_KEY_ULONG; + key.ul = kq->uid; + + /* If this was the last entry, remove the key (the UID) from the + * hash table to signal the queue is empty + */ + ret = hash_delete(kq->qctx->wait_queue_hash, &key); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to remove wait queue for user %"SPRIuid"\n", + kq->uid); + return; + } + + DEBUG(SSSDBG_FUNC_DATA, + "Removed queue for %"SPRIuid" \n", kq->uid); + talloc_free(kq); +} + +static int kcm_op_queue_entry_destructor(struct kcm_ops_queue_entry *entry) +{ + struct kcm_ops_queue_entry *next_entry; + struct tevent_immediate *imm; + + if (entry == NULL) { + return 1; + /* Prevent use-after-free of req when shutting down with non-empty queue */ + } else if (entry->queue->qctx->kctx->rctx->shutting_down) { + return 0; + } + + /* Take the next entry from the queue */ + next_entry = entry->next; + + /* Remove the current entry from the queue */ + DLIST_REMOVE(entry->queue->head, entry); + + if (next_entry == NULL) { + /* If there was no other entry, schedule removal of the queue. Do it + * in another tevent tick to avoid issues with callbacks invoking + * the destructor while another request is touching the queue + */ + imm = tevent_create_immediate(entry->queue); + if (imm == NULL) { + return 1; + } + + tevent_schedule_immediate(imm, entry->queue->ev, queue_removal_cb, entry->queue); + return 0; + } + + /* Otherwise, mark the current head as done to run the next request */ + tevent_req_done(next_entry->req); + return 0; +} + +static struct kcm_ops_queue *kcm_op_queue_get(struct kcm_ops_queue_ctx *qctx, + struct tevent_context *ev, + uid_t uid) +{ + errno_t ret; + hash_key_t key; + hash_value_t value; + struct kcm_ops_queue *kq; + + key.type = HASH_KEY_ULONG; + key.ul = uid; + + ret = hash_lookup(qctx->wait_queue_hash, &key, &value); + switch (ret) { + case HASH_SUCCESS: + if (value.type != HASH_VALUE_PTR) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected hash value type.\n"); + return NULL; + } + + kq = talloc_get_type(value.ptr, struct kcm_ops_queue); + if (kq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid queue pointer\n"); + return NULL; + } + + DEBUG(SSSDBG_TRACE_LIBS, "Found existing queue for this ID\n"); + break; + + case HASH_ERROR_KEY_NOT_FOUND: + /* No request for this UID yet. Enqueue this request in case + * another one comes in and return EOK to run the current request + * immediately + */ + DEBUG(SSSDBG_TRACE_LIBS, "No existing queue for this ID\n"); + + kq = talloc_zero(qctx->wait_queue_hash, struct kcm_ops_queue); + if (kq == NULL) { + return NULL; + } + kq->uid = uid; + kq->qctx = qctx; + kq->ev = ev; + + value.type = HASH_VALUE_PTR; + value.ptr = kq; + + ret = hash_enter(qctx->wait_queue_hash, &key, &value); + if (ret != HASH_SUCCESS) { + DEBUG(SSSDBG_CRIT_FAILURE, "hash_enter failed.\n"); + return NULL; + } + break; + + default: + DEBUG(SSSDBG_CRIT_FAILURE, "hash_lookup failed.\n"); + return NULL; + } + + return kq; +} + +struct kcm_op_queue_state { + struct kcm_ops_queue_entry *entry; +}; + +static errno_t kcm_op_queue_add_req(struct kcm_ops_queue *kq, + struct tevent_req *req); + +/* + * Enqueue a request. + * + * If the request queue /for the given ID/ is empty, that is, if this + * request is the first one in the queue, run the request immediately. + * + * Otherwise just add it to the queue and wait until the previous request + * finishes and only at that point mark the current request as done, which + * will trigger calling the recv function and allow the request to continue. + */ +struct tevent_req *kcm_op_queue_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ops_queue_ctx *qctx, + struct cli_creds *client) +{ + errno_t ret; + struct tevent_req *req; + struct kcm_ops_queue *kq; + struct kcm_op_queue_state *state; + uid_t uid; + + uid = cli_creds_get_uid(client); + + req = tevent_req_create(mem_ctx, &state, struct kcm_op_queue_state); + if (req == NULL) { + return NULL; + } + + DEBUG(SSSDBG_FUNC_DATA, + "Adding request by %"SPRIuid" to the wait queue\n", uid); + + kq = kcm_op_queue_get(qctx, ev, uid); + if (kq == NULL) { + ret = EIO; + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get queue [%d]: %s\n", ret, sss_strerror(ret)); + goto immediate; + } + + ret = kcm_op_queue_add_req(kq, req); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_LIBS, + "Queue was empty, running the request immediately\n"); + goto immediate; + } else if (ret != EAGAIN) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot enqueue request [%d]: %s\n", ret, sss_strerror(ret)); + goto immediate; + } + + DEBUG(SSSDBG_TRACE_LIBS, "Waiting our turn in the queue\n"); + return req; + +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static errno_t kcm_op_queue_add_req(struct kcm_ops_queue *kq, + struct tevent_req *req) +{ + errno_t ret; + struct kcm_op_queue_state *state = tevent_req_data(req, + struct kcm_op_queue_state); + + state->entry = talloc_zero(kq->qctx->wait_queue_hash, struct kcm_ops_queue_entry); + if (state->entry == NULL) { + return ENOMEM; + } + state->entry->req = req; + state->entry->queue = kq; + talloc_set_destructor(state->entry, kcm_op_queue_entry_destructor); + + if (kq->head == NULL) { + /* First entry, will run callback at once */ + ret = EOK; + } else { + /* Will wait for the previous callbacks to finish */ + ret = EAGAIN; + } + + DLIST_ADD_END(kq->head, state->entry, struct kcm_ops_queue_entry *); + return ret; +} + +/* + * The queue recv function is called when this request is 'activated'. The queue + * entry should be allocated on the same memory context as the enqueued request + * to trigger freeing the kcm_ops_queue_entry structure destructor when the + * parent request is done and its tevent_req freed. This would in turn unblock + * the next request in the queue + */ +errno_t kcm_op_queue_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct kcm_ops_queue_entry **_entry) +{ + struct kcm_op_queue_state *state = tevent_req_data(req, + struct kcm_op_queue_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + *_entry = talloc_steal(mem_ctx, state->entry); + return EOK; +} diff --git a/src/responder/kcm/kcmsrv_ops.c b/src/responder/kcm/kcmsrv_ops.c new file mode 100644 index 0000000..8935a79 --- /dev/null +++ b/src/responder/kcm/kcmsrv_ops.c @@ -0,0 +1,2458 @@ +/* + SSSD + + KCM Server - the KCM server operations + + Copyright (C) Red Hat, 2016 + + 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 "config.h" + +#include <krb5/krb5.h> +#include <dhash.h> + +#include "util/sss_iobuf.h" +#include "util/sss_krb5.h" +#include "util/sss_ptr_hash.h" +#include "util/util_creds.h" +#include "responder/kcm/kcmsrv_pvt.h" +#include "responder/kcm/kcmsrv_ops.h" +#include "responder/kcm/kcmsrv_ccache.h" + +struct kcm_op_ctx { + struct kcm_resp_ctx *kcm_data; + struct kcm_conn_data *conn_data; + struct cli_creds *client; + + struct sss_iobuf *input; + struct sss_iobuf *reply; +}; + +/* Each operation follows the same pattern and is implemented using + * functions with this prototype. The operation receives an op_ctx + * that serves as a state of the operation and can be used to keep + * track of any temporary data. The operation writes its output data + * into the op_ctx reply IO buffer and returns the op_ret status code + * separately. + * + * The operation always returns EOK unless an internal error occurs, + * the result of the operation is stored in the op_ret variable + */ +typedef struct tevent_req* +(*kcm_srv_send_method)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_op_ctx *op_ctx); +typedef errno_t +(*kcm_srv_recv_method)(struct tevent_req *req, + uint32_t *_op_ret); + +struct kcm_op { + const char *name; + kcm_srv_send_method fn_send; + kcm_srv_recv_method fn_recv; +}; + +struct kcm_cmd_state { + struct kcm_op *op; + struct tevent_context *ev; + + struct kcm_ops_queue_entry *queue_entry; + struct kcm_op_ctx *op_ctx; + struct sss_iobuf *reply; + + uint32_t op_ret; +}; + +static void kcm_cmd_queue_done(struct tevent_req *subreq); +static void kcm_cmd_done(struct tevent_req *subreq); + +struct tevent_req *kcm_cmd_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ops_queue_ctx *qctx, + struct kcm_resp_ctx *kcm_data, + struct kcm_conn_data *conn_data, + struct cli_creds *client, + struct kcm_data *input, + struct kcm_op *op) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct kcm_cmd_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct kcm_cmd_state); + if (req == NULL) { + return NULL; + } + state->op = op; + state->ev = ev; + + if (op == NULL) { + ret = EINVAL; + goto immediate; + } + + DEBUG(SSSDBG_TRACE_FUNC, "KCM operation %s\n", op->name); + DEBUG(SSSDBG_TRACE_LIBS, "%zu bytes on KCM input\n", input->length); + + state->reply = sss_iobuf_init_empty(state, + KCM_PACKET_INITIAL_SIZE, + KCM_PACKET_MAX_SIZE); + if (state->reply == NULL) { + ret = ENOMEM; + goto immediate; + } + + if (op->fn_send == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, + "KCM op %s has no handler\n", kcm_opt_name(op)); + ret = ERR_KCM_OP_NOT_IMPLEMENTED; + goto immediate; + } + + /* Allocating op_ctx on the heap makes it possible for operations to use + * op_ctx as their temporary context and avoid tmp_ctx altogether + */ + state->op_ctx = talloc_zero(state, struct kcm_op_ctx); + if (state->op_ctx == NULL) { + ret = ENOMEM; + goto immediate; + } + + state->op_ctx->kcm_data = kcm_data; + state->op_ctx->conn_data = conn_data; + state->op_ctx->client = client; + + state->op_ctx->input = sss_iobuf_init_readonly(state->op_ctx, + input->data, + input->length); + if (state->op_ctx->input == NULL) { + ret = ENOMEM; + goto immediate; + } + + /* + * The internal operation returns the opcode and the buffer separately. + * The KCM server reply to the client also always contains zero if the + * operation ran to completion, both are uint32_t. + * FIXME: + * Alternatively, we could extend iobuf API so that we can just pass + * the reply's buffer+sizeof(2*uint32_t) and avoid the useless allocations + */ + state->op_ctx->reply = sss_iobuf_init_empty( + state, + KCM_PACKET_INITIAL_SIZE, + KCM_PACKET_MAX_SIZE - 2*sizeof(uint32_t)); + if (state->op_ctx->reply == NULL) { + ret = ENOMEM; + goto immediate; + } + + subreq = kcm_op_queue_send(state, ev, qctx, client); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_cmd_queue_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kcm_cmd_queue_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); + struct kcm_cmd_state *state = tevent_req_data(req, struct kcm_cmd_state); + errno_t ret; + + /* When this request finishes, it frees the queue_entry which unblocks + * other requests by the same UID + */ + ret = kcm_op_queue_recv(subreq, state, &state->queue_entry); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot acquire queue slot\n"); + tevent_req_error(req, ret); + return; + } + + subreq = state->op->fn_send(state, state->ev, state->op_ctx); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, kcm_cmd_done, req); +} + +static void kcm_cmd_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); + struct kcm_cmd_state *state = tevent_req_data(req, struct kcm_cmd_state); + errno_t ret; + krb5_error_code kerr; + + ret = state->op->fn_recv(subreq, &state->op_ret); + talloc_free(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "op receive function failed [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "KCM operation %s returned [%d]: %s\n", + kcm_opt_name(state->op), state->op_ret, sss_strerror(state->op_ret)); + + kerr = sss2krb5_error(state->op_ret); + + /* The first four bytes of the reply is the operation status code */ + ret = sss_iobuf_write_uint32(state->reply, htobe32(kerr)); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + ret = sss_iobuf_write_len(state->reply, + sss_iobuf_get_data(state->op_ctx->reply), + sss_iobuf_get_len(state->op_ctx->reply)); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t kcm_cmd_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct sss_iobuf **_reply) +{ + struct kcm_cmd_state *state = NULL; + + TEVENT_REQ_RETURN_ON_ERROR(req); + + state = tevent_req_data(req, struct kcm_cmd_state); + + *_reply = talloc_steal(mem_ctx, state->reply); + return EOK; +} + +/* ======= KCM operations ======= */ + +/* Operations that don't return any extra information except for the op_ret + * can use this macro in the _recv function to avoid code duplication + */ +#define KCM_OP_RET_FROM_TYPE(req, state_type, _op_ret_out) do { \ + state_type *state = NULL; \ + state = tevent_req_data(req, state_type); \ + TEVENT_REQ_RETURN_ON_ERROR(req); \ + *_op_ret_out = state->op_ret; \ + return EOK; \ +} while(0); + +struct kcm_op_common_state { + uint32_t op_ret; + struct kcm_op_ctx *op_ctx; + struct tevent_context *ev; +}; + +static errno_t kcm_op_common_recv(struct tevent_req *req, + uint32_t *_op_ret) +{ + struct kcm_op_common_state *state = tevent_req_data(req, + struct kcm_op_common_state); + TEVENT_REQ_RETURN_ON_ERROR(req); + *_op_ret = state->op_ret; + return EOK; +} + +/* () -> (name) */ +static void kcm_op_gen_new_done(struct tevent_req *subreq); + +static struct tevent_req *kcm_op_gen_new_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_op_ctx *op_ctx) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct kcm_op_common_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct kcm_op_common_state); + if (req == NULL) { + return NULL; + } + state->op_ctx = op_ctx; + + subreq = kcm_ccdb_nextid_send(state, ev, + op_ctx->kcm_data->db, + op_ctx->client); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_op_gen_new_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kcm_op_gen_new_done(struct tevent_req *subreq) +{ + errno_t ret; + char *newid; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_op_common_state *state = tevent_req_data(req, + struct kcm_op_common_state); + + ret = kcm_ccdb_nextid_recv(subreq, state, &newid); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot generate a new ID [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_LIBS, "Generated a new ID %s\n", newid); + + ret = sss_iobuf_write_stringz(state->op_ctx->reply, newid); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot write generated ID %d: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + state->op_ret = EOK; + tevent_req_done(req); +} + +/* (princ) -> () */ +struct kcm_op_initialize_state { + uint32_t op_ret; + struct kcm_op_ctx *op_ctx; + struct tevent_context *ev; + + struct kcm_ccache *new_cc; + const char *name; + krb5_principal princ; +}; + +static void kcm_op_initialize_got_byname(struct tevent_req *subreq); +static void kcm_op_initialize_cc_create_done(struct tevent_req *subreq); +static void kcm_op_initialize_cc_delete_done(struct tevent_req *subreq); +static void kcm_op_initialize_fill_princ_step(struct tevent_req *req); +static void kcm_op_initialize_fill_princ_done(struct tevent_req *subreq); +static void kcm_op_initialize_create_step(struct tevent_req *req); +static void kcm_op_initialize_got_default(struct tevent_req *subreq); +static void kcm_op_initialize_set_default_done(struct tevent_req *subreq); + +static struct tevent_req *kcm_op_initialize_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_op_ctx *op_ctx) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct kcm_op_initialize_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct kcm_op_initialize_state); + if (req == NULL) { + return NULL; + } + state->op_ctx = op_ctx; + state->ev = ev; + + ret = sss_iobuf_read_stringz(op_ctx->input, &state->name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot read input name [%d]: %s\n", + ret, sss_strerror(ret)); + goto immediate; + } + DEBUG(SSSDBG_TRACE_LIBS, "Initializing ccache %s\n", state->name); + + ret = kcm_check_name(state->name, op_ctx->client); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Name %s is malformed [%d]: %s\n", + state->name, ret, sss_strerror(ret)); + goto immediate; + } + + ret = sss_krb5_unmarshal_princ(op_ctx, op_ctx->input, &state->princ); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot unmarshal principal [%d]: %s\n", + ret, sss_strerror(ret)); + goto immediate; + } + + subreq = kcm_ccdb_getbyname_send(state, ev, + op_ctx->kcm_data->db, + op_ctx->client, + state->name); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_op_initialize_got_byname, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kcm_op_initialize_got_byname(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_op_initialize_state *state = tevent_req_data(req, + struct kcm_op_initialize_state); + bool ok; + uuid_t uuid; + + ret = kcm_ccdb_getbyname_recv(subreq, state, &state->new_cc); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get ccache by name [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (state->new_cc != NULL) { + if (kcm_cc_get_client_principal(state->new_cc) == NULL) { + /* This is a cache that was pre-created w/o a principal (sshd does this), + * let's fill in the principal and set the cache as default if not + * already + */ + kcm_op_initialize_fill_princ_step(req); + return; + } + + ok = kcm_cc_access(state->new_cc, state->op_ctx->client); + if (!ok) { + state->op_ret = EACCES; + tevent_req_done(req); + return; + } + + /* `uuid` is output arg and isn't read in kcm_cc_get_uuid() but + * since libuuid is opaque for cppcheck it generates false positive here + */ + /* cppcheck-suppress uninitvar */ + ret = kcm_cc_get_uuid(state->new_cc, uuid); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get new ccache UUID [%d]: %s\n", + ret, sss_strerror(ret)); + return; + } + + /* Nuke any previous cache and its contents during initialization */ + subreq = kcm_ccdb_delete_cc_send(state, + state->ev, + state->op_ctx->kcm_data->db, + state->op_ctx->client, + uuid); + if (subreq == NULL) { + tevent_req_error(req, ret); + return; + } + tevent_req_set_callback(subreq, kcm_op_initialize_cc_delete_done, req); + return; + } + + kcm_op_initialize_create_step(req); +} + +static void kcm_op_initialize_cc_delete_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + errno_t ret; + + ret = kcm_ccdb_delete_cc_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot delete ccache from the db %d: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + kcm_op_initialize_create_step(req); +} + +static void kcm_op_initialize_fill_princ_step(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct kcm_op_initialize_state *state = tevent_req_data(req, + struct kcm_op_initialize_state); + errno_t ret; + struct kcm_mod_ctx *mod_ctx; + uuid_t uuid; + + mod_ctx = kcm_mod_ctx_new(state); + if (mod_ctx == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + mod_ctx->client = state->princ; + + /* `uuid` is output arg and isn't read in kcm_cc_get_uuid() but + * since libuuid is opaque for cppcheck it generates false positive here + */ + /* cppcheck-suppress uninitvar */ + ret = kcm_cc_get_uuid(state->new_cc, uuid); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + subreq = kcm_ccdb_mod_cc_send(state, + state->ev, + state->op_ctx->kcm_data->db, + state->op_ctx->client, + uuid, + mod_ctx); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, kcm_op_initialize_fill_princ_done, req); +} + +static void kcm_op_initialize_fill_princ_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_op_initialize_state *state = tevent_req_data(req, + struct kcm_op_initialize_state); + errno_t ret; + + ret = kcm_ccdb_mod_cc_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot modify ccache [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + /* Make sure the cache we just initialized is the default one */ + subreq = kcm_ccdb_get_default_send(state, state->ev, + state->op_ctx->kcm_data->db, + state->op_ctx->client); + if (subreq == NULL) { + tevent_req_error(req, ret); + return; + } + tevent_req_set_callback(subreq, kcm_op_initialize_got_default, req); +} + +static void kcm_op_initialize_create_step(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct kcm_op_initialize_state *state = tevent_req_data(req, + struct kcm_op_initialize_state); + errno_t ret; + + ret = kcm_cc_new(state->op_ctx, + state->op_ctx->kcm_data->k5c, + state->op_ctx->client, + state->name, + state->princ, + &state->new_cc); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot create new ccache %d: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + subreq = kcm_ccdb_create_cc_send(state, + state->ev, + state->op_ctx->kcm_data->db, + state->op_ctx->client, + state->new_cc); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, kcm_op_initialize_cc_create_done, req); +} + +static void kcm_op_initialize_cc_create_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_op_initialize_state *state = tevent_req_data(req, + struct kcm_op_initialize_state); + errno_t ret; + + ret = kcm_ccdb_create_cc_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot add ccache to db %d: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + /* If there was no previous default ccache, set this one as default */ + subreq = kcm_ccdb_get_default_send(state, state->ev, + state->op_ctx->kcm_data->db, + state->op_ctx->client); + if (subreq == NULL) { + tevent_req_error(req, ret); + return; + } + tevent_req_set_callback(subreq, kcm_op_initialize_got_default, req); +} + +static void kcm_op_initialize_got_default(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_op_initialize_state *state = tevent_req_data(req, + struct kcm_op_initialize_state); + errno_t ret; + uuid_t dfl_uuid; + uuid_t old_dfl_uuid; + + ret = kcm_ccdb_get_default_recv(subreq, &old_dfl_uuid); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get default ccache [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (uuid_is_null(old_dfl_uuid)) { + /* If there was no previous default ccache, switch to the initialized + * one by default + */ + /* `dfl_uuid` is output arg and isn't read in kcm_cc_get_uuid() but + * since libuuid is opaque for cppcheck it generates false positive here + */ + /* cppcheck-suppress uninitvar */ + ret = kcm_cc_get_uuid(state->new_cc, dfl_uuid); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get new ccache UUID [%d]: %s\n", + ret, sss_strerror(ret)); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "The default ccached was not set, switching to the " + "initialized\n"); + subreq = kcm_ccdb_set_default_send(state, + state->ev, + state->op_ctx->kcm_data->db, + state->op_ctx->client, + dfl_uuid); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, kcm_op_initialize_set_default_done, req); + return; + } + + /* ENOENT, done */ + state->op_ret = EOK; + tevent_req_done(req); +} + +static void kcm_op_initialize_set_default_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_op_initialize_state *state = tevent_req_data(req, + struct kcm_op_initialize_state); + errno_t ret; + + ret = kcm_ccdb_set_default_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot set default ccache %d: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + state->op_ret = EOK; + tevent_req_done(req); +} + +static errno_t kcm_op_initialize_recv(struct tevent_req *req, + uint32_t *_op_ret) +{ + KCM_OP_RET_FROM_TYPE(req, struct kcm_op_initialize_state, _op_ret); +} + +/* (name) -> () */ +static void kcm_op_destroy_getbyname_done(struct tevent_req *subreq); +static void kcm_op_destroy_delete_done(struct tevent_req *subreq); + +static struct tevent_req *kcm_op_destroy_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_op_ctx *op_ctx) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct kcm_op_common_state *state = NULL; + errno_t ret; + const char *name; + + req = tevent_req_create(mem_ctx, &state, struct kcm_op_common_state); + if (req == NULL) { + return NULL; + } + state->op_ctx = op_ctx; + state->ev = ev; + + ret = sss_iobuf_read_stringz(op_ctx->input, &name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot unmarshall input name [%d]: %s\n", + ret, sss_strerror(ret)); + goto immediate; + } + + DEBUG(SSSDBG_TRACE_LIBS, "Destroying credentials of %s\n", name); + + subreq = kcm_ccdb_uuid_by_name_send(state, ev, + op_ctx->kcm_data->db, + op_ctx->client, + name); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_op_destroy_getbyname_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kcm_op_destroy_getbyname_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_op_common_state *state = tevent_req_data(req, + struct kcm_op_common_state); + uuid_t uuid; + + /* `uuid` is output arg and isn't read in kcm_ccdb_uuid_by_name_recv() but + * since libuuid is opaque for cppcheck it generates false positive here + */ + /* cppcheck-suppress uninitvar */ + ret = kcm_ccdb_uuid_by_name_recv(subreq, state, uuid); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot get matching ccache [%d]: %s\n", + ret, sss_strerror(ret)); + ret = ERR_NO_MATCHING_CREDS; + tevent_req_error(req, ret); + return; + } + + subreq = kcm_ccdb_delete_cc_send(state, + state->ev, + state->op_ctx->kcm_data->db, + state->op_ctx->client, + uuid); + if (subreq == NULL) { + tevent_req_error(req, ret); + return; + } + tevent_req_set_callback(subreq, kcm_op_destroy_delete_done, req); +} + +static void kcm_op_destroy_delete_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_op_common_state *state = tevent_req_data(req, + struct kcm_op_common_state); + + ret = kcm_ccdb_delete_cc_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot delete ccache from the db [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + state->op_ret = EOK; + tevent_req_done(req); +} + +/* (name, cred) -> () */ +struct kcm_op_store_state { + uint32_t op_ret; + struct kcm_op_ctx *op_ctx; + struct tevent_context *ev; + + struct sss_iobuf *cred_blob; +}; + +static void kcm_op_store_getbyname_done(struct tevent_req *subreq); +static void kcm_op_store_done(struct tevent_req *subreq); + +static struct tevent_req *kcm_op_store_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_op_ctx *op_ctx) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct kcm_op_store_state *state = NULL; + errno_t ret; + const char *name; + size_t creds_len; + + req = tevent_req_create(mem_ctx, &state, struct kcm_op_store_state); + if (req == NULL) { + return NULL; + } + state->op_ctx = op_ctx; + state->ev = ev; + + ret = sss_iobuf_read_stringz(op_ctx->input, &name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot unmarshall input name [%d]: %s\n", + ret, sss_strerror(ret)); + goto immediate; + } + + DEBUG(SSSDBG_TRACE_LIBS, "Storing credentials for %s\n", name); + + creds_len = sss_iobuf_get_size(op_ctx->input) - strlen(name) -1; + if (creds_len > KCM_PACKET_MAX_SIZE) { + /* Protects against underflows and in general adds sanity */ + ret = E2BIG; + goto immediate; + } + + state->cred_blob = sss_iobuf_init_empty(state, + creds_len, + creds_len); + if (state->cred_blob == NULL) { + ret = ENOMEM; + goto immediate; + } + + ret = sss_iobuf_read(op_ctx->input, + creds_len, + sss_iobuf_get_data(state->cred_blob), + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot unmarshall input cred blob [%d]: %s\n", + ret, sss_strerror(ret)); + goto immediate; + } + + subreq = kcm_ccdb_uuid_by_name_send(state, ev, + op_ctx->kcm_data->db, + op_ctx->client, + name); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_op_store_getbyname_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kcm_op_store_getbyname_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_op_store_state *state = tevent_req_data(req, + struct kcm_op_store_state); + uuid_t uuid; + + /* `uuid` is output arg and isn't read in kcm_ccdb_uuid_by_name_recv() but + * since libuuid is opaque for cppcheck it generates false positive here + */ + /* cppcheck-suppress uninitvar */ + ret = kcm_ccdb_uuid_by_name_recv(subreq, state, uuid); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get ccache by name [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + subreq = kcm_ccdb_store_cred_blob_send(state, state->ev, + state->op_ctx->kcm_data->db, + state->op_ctx->client, + uuid, + state->cred_blob); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, kcm_op_store_done, req); +} + +static void kcm_op_store_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_op_store_state *state = tevent_req_data(req, + struct kcm_op_store_state); + + ret = kcm_ccdb_store_cred_blob_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot store credentials [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + state->op_ret = EOK; + tevent_req_done(req); +} + +static errno_t kcm_op_store_recv(struct tevent_req *req, + uint32_t *_op_ret) +{ + KCM_OP_RET_FROM_TYPE(req, struct kcm_op_store_state, _op_ret); +} + +/* (name) -> (princ) */ +static void kcm_op_get_principal_getbyname_done(struct tevent_req *subreq); + +static struct tevent_req *kcm_op_get_principal_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_op_ctx *op_ctx) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct kcm_op_common_state *state = NULL; + errno_t ret; + const char *name; + + req = tevent_req_create(mem_ctx, &state, struct kcm_op_common_state); + if (req == NULL) { + return NULL; + } + state->op_ctx = op_ctx; + + ret = sss_iobuf_read_stringz(op_ctx->input, &name); + if (ret != EOK) { + goto immediate; + } + DEBUG(SSSDBG_TRACE_LIBS, "Requested principal %s\n", name); + + subreq = kcm_ccdb_getbyname_send(state, ev, + op_ctx->kcm_data->db, + op_ctx->client, + name); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_op_get_principal_getbyname_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kcm_op_get_principal_getbyname_done(struct tevent_req *subreq) +{ + errno_t ret; + struct kcm_ccache *cc; + krb5_principal princ; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_op_common_state *state = tevent_req_data(req, + struct kcm_op_common_state); + + ret = kcm_ccdb_getbyname_recv(subreq, state, &cc); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get ccache by name [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (cc == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "No credentials by that name\n"); + state->op_ret = ERR_NO_MATCHING_CREDS; + tevent_req_done(req); + return; + } + + /* Marshall the principal to the reply */ + princ = kcm_cc_get_client_principal(cc); + if (princ == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Credentials with no principal?\n"); + tevent_req_error(req, EIO); + return; + } + + ret = sss_krb5_marshal_princ(princ, state->op_ctx->reply); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot marshall principal [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + state->op_ret = EOK; + tevent_req_done(req); +} + +static void +kcm_creds_table_delete_cb(hash_entry_t *item, + hash_destroy_enum deltype, + void *pvt) +{ + /* Delete the old credential if it is being overwritten. */ + talloc_free(item->value.ptr); +} + +/* Store credentials in a hash table. + * + * If the table already exist we add the new credentials to the table and + * overwrite the ones that already exist. This allows us to correctly serve + * also parallel GET_CRED_UUID_LIST requests from the same connection since + * it will have its own uuid list and cursor on the client side and we make + * all uuid (old, updated and newly added) available. + */ +static errno_t +kcm_creds_to_table(TALLOC_CTX *mem_ctx, + struct kcm_cred *creds, + hash_table_t **_table) +{ + char str[UUID_STR_SIZE]; + uuid_t uuid; + errno_t ret; + + if (*_table == NULL) { + *_table = sss_ptr_hash_create(mem_ctx, kcm_creds_table_delete_cb, NULL); + if (*_table == NULL) { + return ENOMEM; + } + } + + for (struct kcm_cred *crd = creds; + crd != NULL; + crd = kcm_cc_next_cred(crd)) { + ret = kcm_cred_get_uuid(crd, uuid); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Credential has no UUID, skipping\n"); + continue; + } + uuid_unparse(uuid, str); + + ret = sss_ptr_hash_add_or_override(*_table, str, crd, struct kcm_cred); + if (ret != EOK) { + return ret; + } + + talloc_steal(*_table, crd); + } + + return EOK; +} + +static struct kcm_cred * +kcm_creds_lookup(hash_table_t *table, uuid_t uuid) +{ + char str[UUID_STR_SIZE]; + + if (uuid == NULL) { + return NULL; + } + + uuid_unparse(uuid, str); + return sss_ptr_hash_lookup(table, str, struct kcm_cred); +} + +/* (name) -> (uuid, ...) */ +static void kcm_op_get_cred_uuid_list_getbyname_done(struct tevent_req *subreq); + +static struct tevent_req * +kcm_op_get_cred_uuid_list_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_op_ctx *op_ctx) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct kcm_op_common_state *state = NULL; + errno_t ret; + const char *name; + + req = tevent_req_create(mem_ctx, &state, struct kcm_op_common_state); + if (req == NULL) { + return NULL; + } + state->op_ctx = op_ctx; + + ret = sss_iobuf_read_stringz(op_ctx->input, &name); + if (ret != EOK) { + goto immediate; + } + + DEBUG(SSSDBG_TRACE_LIBS, "Returning UUID list for %s\n", name); + + subreq = kcm_ccdb_getbyname_send(state, ev, + op_ctx->kcm_data->db, + op_ctx->client, + name); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_op_get_cred_uuid_list_getbyname_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kcm_op_get_cred_uuid_list_getbyname_done(struct tevent_req *subreq) +{ + errno_t ret; + struct kcm_ccache *cc; + struct kcm_cred *crd; + struct kcm_conn_data *conn_data; + uuid_t uuid; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_op_common_state *state = tevent_req_data(req, + struct kcm_op_common_state); + + conn_data = state->op_ctx->conn_data; + + ret = kcm_ccdb_getbyname_recv(subreq, state, &cc); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get ccache by name [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (cc == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "No ccache by that name\n"); + state->op_ret = ERR_NO_CREDS; + tevent_req_done(req); + return; + } + + ret = kcm_creds_to_table(conn_data, kcm_cc_get_cred(cc), &conn_data->creds); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to build credentials hash table " + "[%d]: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + for (crd = kcm_cc_get_cred(cc); + crd != NULL; + crd = kcm_cc_next_cred(crd)) { + ret = kcm_cred_get_uuid(crd, uuid); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Credential has no UUID, skipping\n"); + continue; + } + + kcm_debug_uuid(uuid); + + ret = sss_iobuf_write_len(state->op_ctx->reply, + uuid, UUID_BYTES); + if (ret != EOK) { + char uuid_errbuf[UUID_STR_SIZE]; + uuid_parse(uuid_errbuf, uuid); + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot marshall UUID %s [%d]: %s\n", + uuid_errbuf, ret, sss_strerror(ret)); + continue; + } + } + state->op_ret = EOK; + tevent_req_done(req); +} + +static errno_t +kcm_op_get_cred_by_uuid_reply(struct kcm_cred *crd, + struct sss_iobuf *reply) +{ + struct sss_iobuf *cred_blob; + errno_t ret; + + cred_blob = kcm_cred_get_creds(crd); + if (cred_blob == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Credentials lack the creds blob\n"); + return ERR_NO_CREDS; + } + + ret = sss_iobuf_write_len(reply, sss_iobuf_get_data(cred_blob), + sss_iobuf_get_size(cred_blob)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot write ccache blob [%d]: %s\n", + ret, sss_strerror(ret)); + } + + return ret; +} + +struct kcm_op_get_cred_by_uuid_state { + struct kcm_op_common_state common; + uuid_t uuid; +}; + +/* (name, uuid) -> (cred) */ +static void kcm_op_get_cred_by_uuid_getbyname_done(struct tevent_req *subreq); + +static struct tevent_req * +kcm_op_get_cred_by_uuid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_op_ctx *op_ctx) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct kcm_op_get_cred_by_uuid_state *state; + struct kcm_cred *crd; + errno_t ret; + const char *name; + + req = tevent_req_create(mem_ctx, &state, + struct kcm_op_get_cred_by_uuid_state); + if (req == NULL) { + return NULL; + } + state->common.op_ctx = op_ctx; + + ret = sss_iobuf_read_stringz(op_ctx->input, &name); + if (ret != EOK) { + goto immediate; + } + + ret = sss_iobuf_read_len(state->common.op_ctx->input, UUID_BYTES, + state->uuid); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot read input UUID [%d]: %s\n", + ret, sss_strerror(ret)); + goto immediate; + } + + if (op_ctx->conn_data->creds != NULL) { + crd = kcm_creds_lookup(op_ctx->conn_data->creds, state->uuid); + if (crd == NULL) { + /* This should not happen, it can only happen if wrong UUID was + * requested which suggests bug in the caller application. */ + DEBUG(SSSDBG_MINOR_FAILURE, "No credentials by that UUID\n"); + kcm_debug_uuid(state->uuid); + state->common.op_ret = ERR_KCM_CC_END; + ret = EOK; + goto immediate; + } else { + ret = kcm_op_get_cred_by_uuid_reply(crd, op_ctx->reply); + if (ret == ERR_NO_CREDS) { + state->common.op_ret = ret; + ret = EOK; + } + goto immediate; + } + } + + DEBUG(SSSDBG_TRACE_LIBS, "Returning creds by UUID for %s\n", name); + + subreq = kcm_ccdb_getbyname_send(state, ev, + op_ctx->kcm_data->db, + op_ctx->client, + name); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_op_get_cred_by_uuid_getbyname_done, req); + return req; + +immediate: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + return req; +} + +static void kcm_op_get_cred_by_uuid_getbyname_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_op_get_cred_by_uuid_state *state = tevent_req_data(req, + struct kcm_op_get_cred_by_uuid_state); + errno_t ret; + struct kcm_ccache *cc; + struct kcm_cred *crd; + struct kcm_conn_data *conn_data; + + conn_data = state->common.op_ctx->conn_data; + + ret = kcm_ccdb_getbyname_recv(subreq, state, &cc); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get ccache by name [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + ret = kcm_creds_to_table(conn_data, kcm_cc_get_cred(cc), &conn_data->creds); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to build credentials hash table " + "[%d]: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (conn_data->creds != NULL) { + crd = kcm_creds_lookup(conn_data->creds, state->uuid); + if (crd == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "No credentials by that UUID\n"); + kcm_debug_uuid(state->uuid); + state->common.op_ret = ERR_KCM_CC_END; + } else { + ret = kcm_op_get_cred_by_uuid_reply(crd, state->common.op_ctx->reply); + if (ret != EOK && ret != ERR_NO_CREDS) { + tevent_req_error(req, ret); + return; + } + state->common.op_ret = ret; + } + } + + tevent_req_done(req); +} + +static errno_t kcm_op_get_cred_by_uuid_recv(struct tevent_req *req, + uint32_t *_op_ret) +{ + struct kcm_op_get_cred_by_uuid_state *state; + + state = tevent_req_data(req, struct kcm_op_get_cred_by_uuid_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + *_op_ret = state->common.op_ret; + return EOK; +} + +/* (name, flags, credtag) -> () */ +/* FIXME */ +static struct tevent_req * +kcm_op_remove_cred_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_op_ctx *op_ctx) +{ + struct tevent_req *req = NULL; + struct kcm_op_common_state *state = NULL; + + req = tevent_req_create(mem_ctx, &state, struct kcm_op_common_state); + if (req == NULL) { + return NULL; + } + state->op_ctx = op_ctx; + + state->op_ret = ERR_KCM_OP_NOT_IMPLEMENTED; + tevent_req_post(req, ev); + tevent_req_done(req); + return req; +} + +/* () -> (uuid, ...) */ +static void kcm_op_get_cache_uuid_list_done(struct tevent_req *subreq); + +static struct tevent_req * +kcm_op_get_cache_uuid_list_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_op_ctx *op_ctx) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct kcm_op_common_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct kcm_op_common_state); + if (req == NULL) { + return NULL; + } + state->op_ctx = op_ctx; + + DEBUG(SSSDBG_TRACE_LIBS, "Returning full UUID list\n"); + + subreq = kcm_ccdb_list_send(state, ev, + op_ctx->kcm_data->db, + op_ctx->client); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_op_get_cache_uuid_list_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kcm_op_get_cache_uuid_list_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_op_common_state *state = tevent_req_data(req, + struct kcm_op_common_state); + errno_t ret; + uuid_t *uuid_list; + + ret = kcm_ccdb_list_recv(subreq, state, &uuid_list); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot list the ccache DB [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (uuid_list == NULL || uuid_is_null(uuid_list[0])) { + DEBUG(SSSDBG_MINOR_FAILURE, "Nothing to list\n"); + state->op_ret = ERR_NO_MATCHING_CREDS; + tevent_req_done(req); + return; + } + + for (int i = 0; + uuid_is_null(uuid_list[i]) == false; + i++) { + kcm_debug_uuid(uuid_list[i]); + + ret = sss_iobuf_write_len(state->op_ctx->reply, + uuid_list[i], + UUID_BYTES); + if (ret != EOK) { + char uuid_errbuf[UUID_STR_SIZE]; + uuid_parse(uuid_errbuf, uuid_list[i]); + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot marshall UUID %s [%d]: %s\n", + uuid_errbuf, ret, sss_strerror(ret)); + tevent_req_done(req); + return; + } + } + + tevent_req_done(req); +} + +/* (uuid) -> (name) */ +static void kcm_op_get_cache_by_uuid_done(struct tevent_req *subreq); + +static struct tevent_req * +kcm_op_get_cache_by_uuid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_op_ctx *op_ctx) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct kcm_op_common_state *state = NULL; + errno_t ret; + uuid_t uuid_in; + + req = tevent_req_create(mem_ctx, &state, struct kcm_op_common_state); + if (req == NULL) { + return NULL; + } + state->op_ctx = op_ctx; + + DEBUG(SSSDBG_TRACE_LIBS, "Retrieving cache by UUID\n"); + + ret = sss_iobuf_read_len(op_ctx->input, + UUID_BYTES, uuid_in); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot read input UUID [%d]: %s\n", + ret, sss_strerror(ret)); + goto immediate; + } + kcm_debug_uuid(uuid_in); + + subreq = kcm_ccdb_getbyuuid_send(state, ev, + op_ctx->kcm_data->db, + op_ctx->client, + uuid_in); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_op_get_cache_by_uuid_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kcm_op_get_cache_by_uuid_done(struct tevent_req *subreq) +{ + errno_t ret; + struct kcm_ccache *cc; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_op_common_state *state = tevent_req_data(req, + struct kcm_op_common_state); + const char *name; + + ret = kcm_ccdb_getbyuuid_recv(subreq, state, &cc); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get ccache by UUID [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (cc == NULL) { + state->op_ret = ERR_KCM_CC_END; + tevent_req_done(req); + return; + } + + name = kcm_cc_get_name(cc); + DEBUG(SSSDBG_TRACE_INTERNAL, "Found %s by UUID\n", name); + + ret = sss_iobuf_write_stringz(state->op_ctx->reply, + name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot write output name [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + state->op_ret = EOK; + tevent_req_done(req); +} + +/* () -> (name) */ +struct kcm_op_get_default_ccache_state { + uint32_t op_ret; + struct kcm_op_ctx *op_ctx; + struct tevent_context *ev; + + const char *name; +}; + +static void kcm_op_get_get_default_done(struct tevent_req *subreq); +static void kcm_op_get_default_ccache_byuuid_done(struct tevent_req *subreq); +static void kcm_op_get_default_ccache_list_done(struct tevent_req *subreq); +static errno_t +kcm_op_get_default_ccache_reply_step(struct kcm_op_get_default_ccache_state *state); + +static struct tevent_req * +kcm_op_get_default_ccache_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_op_ctx *op_ctx) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct kcm_op_get_default_ccache_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct kcm_op_get_default_ccache_state); + if (req == NULL) { + return NULL; + } + state->op_ctx = op_ctx; + state->ev = ev; + + DEBUG(SSSDBG_TRACE_LIBS, "Getting client's default ccache\n"); + + subreq = kcm_ccdb_get_default_send(state, ev, + state->op_ctx->kcm_data->db, + state->op_ctx->client); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_op_get_get_default_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kcm_op_get_get_default_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); + struct kcm_op_get_default_ccache_state *state = tevent_req_data(req, + struct kcm_op_get_default_ccache_state); + errno_t ret; + uuid_t dfl_uuid; + + ret = kcm_ccdb_get_default_recv(subreq, &dfl_uuid); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get default ccache [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (uuid_is_null(dfl_uuid) == true) { + /* No cache marked as default -- get an existing ccache for ID + * and treat the default as simply the first one + */ + subreq = kcm_ccdb_list_send(state, state->ev, + state->op_ctx->kcm_data->db, + state->op_ctx->client); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, kcm_op_get_default_ccache_list_done, req); + return; + } + + /* Existing default */ + subreq = kcm_ccdb_name_by_uuid_send(state, + state->ev, + state->op_ctx->kcm_data->db, + state->op_ctx->client, + dfl_uuid); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, kcm_op_get_default_ccache_byuuid_done, req); + return; +} + +static void kcm_op_get_default_ccache_byuuid_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); + struct kcm_op_get_default_ccache_state *state = tevent_req_data(req, + struct kcm_op_get_default_ccache_state); + errno_t ret; + + ret = kcm_ccdb_name_by_uuid_recv(subreq, state, &state->name); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get ccahe by UUID [%d]: %s\n", + ret, sss_strerror(ret)); + /* Instead of failing the whole operation, return the first + * ccache as a fallback + */ + subreq = kcm_ccdb_list_send(state, state->ev, + state->op_ctx->kcm_data->db, + state->op_ctx->client); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, kcm_op_get_default_ccache_list_done, req); + return; + } + + ret = kcm_op_get_default_ccache_reply_step(state); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static void kcm_op_get_default_ccache_list_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); + struct kcm_op_get_default_ccache_state *state = tevent_req_data(req, + struct kcm_op_get_default_ccache_state); + errno_t ret; + uuid_t *uuid_list; + + ret = kcm_ccdb_list_recv(subreq, state, &uuid_list); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot list ccaches [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (uuid_list == NULL || uuid_is_null(uuid_list[0])) { + /* No cache at all, just send back a reply */ + ret = kcm_op_get_default_ccache_reply_step(state); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; + } + + /* Otherwise resolve the first cache and use it as a default */ + subreq = kcm_ccdb_name_by_uuid_send(state, + state->ev, + state->op_ctx->kcm_data->db, + state->op_ctx->client, + uuid_list[0]); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, kcm_op_get_default_ccache_byuuid_done, req); + return; +} + +static errno_t +kcm_op_get_default_ccache_reply_step(struct kcm_op_get_default_ccache_state *state) +{ + errno_t ret; + + if (state->name == NULL) { + state->name = talloc_asprintf(state, + "%"SPRIuid, + cli_creds_get_uid(state->op_ctx->client)); + if (state->name == NULL) { + return ENOMEM; + } + } + DEBUG(SSSDBG_TRACE_INTERNAL, "The default ccache is %s\n", state->name); + + ret = sss_iobuf_write_stringz(state->op_ctx->reply, state->name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot write output name [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + return EOK; +} + +static errno_t kcm_op_get_default_ccache_recv(struct tevent_req *req, + uint32_t *_op_ret) +{ + KCM_OP_RET_FROM_TYPE(req, struct kcm_op_get_default_ccache_state, _op_ret); +} + +/* (name) -> () */ +static void kcm_op_set_default_ccache_getbyname_done(struct tevent_req *subreq); +static void kcm_op_set_default_create_step(struct tevent_req *req); +static void kcm_op_set_default_create_step_done(struct tevent_req *subreq); +static void kcm_op_set_default_step(struct tevent_req *req); +static void kcm_op_set_default_done(struct tevent_req *subreq); + +struct kcm_op_set_default_ccache_state { + uint32_t op_ret; + struct kcm_op_ctx *op_ctx; + struct tevent_context *ev; + + const char *name; + uuid_t dfl_uuid; + struct kcm_ccache *new_cc; +}; + +static struct tevent_req * +kcm_op_set_default_ccache_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_op_ctx *op_ctx) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct kcm_op_set_default_ccache_state *state = NULL; + errno_t ret; + + req = tevent_req_create(mem_ctx, + &state, + struct kcm_op_set_default_ccache_state); + if (req == NULL) { + return NULL; + } + state->op_ctx = op_ctx; + state->ev = ev; + + ret = sss_iobuf_read_stringz(op_ctx->input, &state->name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot read input name [%d]: %s\n", + ret, sss_strerror(ret)); + goto immediate; + } + DEBUG(SSSDBG_TRACE_LIBS, "Setting default ccache %s\n", state->name); + + subreq = kcm_ccdb_uuid_by_name_send(state, ev, + op_ctx->kcm_data->db, + op_ctx->client, + state->name); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_op_set_default_ccache_getbyname_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kcm_op_set_default_ccache_getbyname_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_op_set_default_ccache_state *state = tevent_req_data(req, + struct kcm_op_set_default_ccache_state); + + ret = kcm_ccdb_uuid_by_name_recv(subreq, state, state->dfl_uuid); + talloc_zfree(subreq); + if (ret == ERR_NO_CREDS) { + DEBUG(SSSDBG_TRACE_LIBS, + "The ccache does not exist, creating a new one\n"); + kcm_op_set_default_create_step(req); + return; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get ccache by name [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + kcm_op_set_default_step(req); +} + +static void kcm_op_set_default_create_step(struct tevent_req *req) +{ + errno_t ret; + struct tevent_req *subreq; + struct kcm_op_set_default_ccache_state *state = tevent_req_data(req, + struct kcm_op_set_default_ccache_state); + + /* Only allow to create ccaches for 'self' */ + ret = kcm_check_name(state->name, state->op_ctx->client); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Name %s is malformed [%d]: %s\n", + state->name, ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + ret = kcm_cc_new(state->op_ctx, + state->op_ctx->kcm_data->k5c, + state->op_ctx->client, + state->name, + NULL, + &state->new_cc); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot create new ccache %d: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + subreq = kcm_ccdb_create_cc_send(state, + state->ev, + state->op_ctx->kcm_data->db, + state->op_ctx->client, + state->new_cc); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, kcm_op_set_default_create_step_done, req); +} + +static void kcm_op_set_default_create_step_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_op_set_default_ccache_state *state = tevent_req_data(req, + struct kcm_op_set_default_ccache_state); + + ret = kcm_ccdb_create_cc_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot add ccache to db %d: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "The ccache was created, switching to it"); + + ret = kcm_cc_get_uuid(state->new_cc, state->dfl_uuid); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get new ccache UUID [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + kcm_op_set_default_step(req); +} + +static void kcm_op_set_default_step(struct tevent_req *req) +{ + struct kcm_op_set_default_ccache_state *state = tevent_req_data(req, + struct kcm_op_set_default_ccache_state); + struct tevent_req *subreq; + + subreq = kcm_ccdb_set_default_send(state, + state->ev, + state->op_ctx->kcm_data->db, + state->op_ctx->client, + state->dfl_uuid); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, kcm_op_set_default_done, req); + return; +} + +static void kcm_op_set_default_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_op_set_default_ccache_state *state = tevent_req_data(req, + struct kcm_op_set_default_ccache_state); + + ret = kcm_ccdb_set_default_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot set default ccache %d: %s\n", ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + state->op_ret = EOK; + tevent_req_done(req); +} + +static errno_t kcm_op_set_default_ccache_recv(struct tevent_req *req, + uint32_t *_op_ret) +{ + KCM_OP_RET_FROM_TYPE(req, struct kcm_op_set_default_ccache_state, _op_ret); +} + +/* (name) -> (offset) */ +static void kcm_op_get_kdc_offset_getbyname_done(struct tevent_req *subreq); + +static struct tevent_req * +kcm_op_get_kdc_offset_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_op_ctx *op_ctx) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct kcm_op_common_state *state = NULL; + errno_t ret; + const char *name; + + req = tevent_req_create(mem_ctx, &state, struct kcm_op_common_state); + if (req == NULL) { + return NULL; + } + state->op_ctx = op_ctx; + + ret = sss_iobuf_read_stringz(op_ctx->input, &name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot read input name [%d]: %s\n", + ret, sss_strerror(ret)); + goto immediate; + } + DEBUG(SSSDBG_TRACE_LIBS, "Requested offset for principal %s\n", name); + + subreq = kcm_ccdb_getbyname_send(state, ev, + op_ctx->kcm_data->db, + op_ctx->client, + name); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_op_get_kdc_offset_getbyname_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kcm_op_get_kdc_offset_getbyname_done(struct tevent_req *subreq) +{ + errno_t ret; + struct kcm_ccache *cc; + int32_t offset; + int32_t offset_be; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_op_common_state *state = tevent_req_data(req, + struct kcm_op_common_state); + + ret = kcm_ccdb_getbyname_recv(subreq, state, &cc); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get matching ccache [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (cc == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "No matching credentials\n"); + state->op_ret = ERR_NO_MATCHING_CREDS; + tevent_req_done(req); + return; + } + + offset = kcm_cc_get_offset(cc); + DEBUG(SSSDBG_TRACE_LIBS, "KDC offset: %"PRIi32"\n", offset); + + offset_be = htobe32(offset); + ret = sss_iobuf_write_int32(state->op_ctx->reply, offset_be); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot write KDC offset [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + state->op_ret = EOK; + tevent_req_done(req); +} + +/* (name, offset) -> () */ +/* () -> (name) */ +struct kcm_op_set_kdc_offset_state { + uint32_t op_ret; + struct kcm_op_ctx *op_ctx; + struct tevent_context *ev; +}; + +static void kcm_op_set_kdc_offset_getbyname_done(struct tevent_req *subreq); +static void kcm_op_set_kdc_offset_mod_done(struct tevent_req *subreq); + +static struct tevent_req * +kcm_op_set_kdc_offset_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_op_ctx *op_ctx) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct kcm_op_set_kdc_offset_state *state = NULL; + errno_t ret; + const char *name; + + req = tevent_req_create(mem_ctx, &state, struct kcm_op_set_kdc_offset_state); + if (req == NULL) { + return NULL; + } + state->op_ctx = op_ctx; + state->ev = ev; + + ret = sss_iobuf_read_stringz(op_ctx->input, &name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot read input name [%d]: %s\n", + ret, sss_strerror(ret)); + goto immediate; + } + DEBUG(SSSDBG_TRACE_LIBS, "Setting offset for principal %s\n", name); + + subreq = kcm_ccdb_uuid_by_name_send(state, ev, + op_ctx->kcm_data->db, + op_ctx->client, + name); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + tevent_req_set_callback(subreq, kcm_op_set_kdc_offset_getbyname_done, req); + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kcm_op_set_kdc_offset_getbyname_done(struct tevent_req *subreq) +{ + errno_t ret; + struct kcm_mod_ctx *mod_ctx; + int32_t offset_be; + uuid_t uuid; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_op_set_kdc_offset_state *state = tevent_req_data(req, + struct kcm_op_set_kdc_offset_state); + + /* `uuid` is output arg and isn't read in kcm_ccdb_uuid_by_name_recv() but + * since libuuid is opaque for cppcheck it generates false positive here + */ + /* cppcheck-suppress uninitvar */ + ret = kcm_ccdb_uuid_by_name_recv(subreq, state, uuid); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot get matching ccache [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + ret = sss_iobuf_read_int32(state->op_ctx->input, &offset_be); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot read KDC offset [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + mod_ctx = kcm_mod_ctx_new(state); + if (mod_ctx == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + mod_ctx->kdc_offset = be32toh(offset_be); + + subreq = kcm_ccdb_mod_cc_send(state, + state->ev, + state->op_ctx->kcm_data->db, + state->op_ctx->client, + uuid, + mod_ctx); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, kcm_op_set_kdc_offset_mod_done, req); +} + +static void kcm_op_set_kdc_offset_mod_done(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct kcm_op_set_kdc_offset_state *state = tevent_req_data(req, + struct kcm_op_set_kdc_offset_state); + + ret = kcm_ccdb_mod_cc_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot modify ccache [%d]: %s\n", + ret, sss_strerror(ret)); + tevent_req_error(req, ret); + return; + } + + state->op_ret = EOK; + tevent_req_done(req); +} + +static errno_t kcm_op_set_kdc_offset_recv(struct tevent_req *req, + uint32_t *_op_ret) +{ + KCM_OP_RET_FROM_TYPE(req, struct kcm_op_set_kdc_offset_state, _op_ret); +} + +static void kcm_op_get_cred_list_done(struct tevent_req *subreq); + +static struct tevent_req * +kcm_op_get_cred_list_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_op_ctx *op_ctx) +{ + struct kcm_op_common_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + const char *name; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct kcm_op_common_state); + if (req == NULL) { + return NULL; + } + state->op_ctx = op_ctx; + + ret = sss_iobuf_read_stringz(op_ctx->input, &name); + if (ret != EOK) { + goto immediate; + } + + DEBUG(SSSDBG_TRACE_LIBS, "Returning credentials for %s\n", name); + + subreq = kcm_ccdb_getbyname_send(state, ev, + op_ctx->kcm_data->db, + op_ctx->client, + name); + if (subreq == NULL) { + ret = ENOMEM; + goto immediate; + } + + tevent_req_set_callback(subreq, kcm_op_get_cred_list_done, req); + + return req; + +immediate: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void kcm_op_get_cred_list_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct kcm_op_common_state *state; + struct kcm_ccache *cc; + struct kcm_cred *crd; + uint32_t num_creds; + struct sss_iobuf *crd_blob; + uint8_t *crd_data; + uint32_t crd_size; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct kcm_op_common_state); + + ret = kcm_ccdb_getbyname_recv(subreq, state, &cc); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot get ccache by name [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + if (cc == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "No ccache by that name\n"); + state->op_ret = ERR_NO_CREDS; + ret = EOK; + goto done; + } + + num_creds = 0; + for (crd = kcm_cc_get_cred(cc); crd != NULL; crd = kcm_cc_next_cred(crd)) { + num_creds++; + } + + ret = sss_iobuf_write_uint32(state->op_ctx->reply, htobe32(num_creds)); + if (ret != EOK) { + goto done; + } + + for (crd = kcm_cc_get_cred(cc); crd != NULL; crd = kcm_cc_next_cred(crd)) { + crd_blob = kcm_cred_get_creds(crd); + if (crd_blob == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Credentials lack the creds blob\n"); + ret = ERR_NO_CREDS; + goto done; + } + + crd_data = sss_iobuf_get_data(crd_blob); + crd_size = sss_iobuf_get_size(crd_blob); + + ret = sss_iobuf_write_uint32(state->op_ctx->reply, htobe32(crd_size)); + if (ret != EOK) { + goto done; + } + + ret = sss_iobuf_write_len(state->op_ctx->reply, crd_data, crd_size); + if (ret != EOK) { + goto done; + } + } + + state->op_ret = EOK; + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static struct kcm_op kcm_optable[] = { + { "NOOP", NULL, NULL }, + { "GET_NAME", NULL, NULL }, + { "RESOLVE", NULL, NULL }, + { "GEN_NEW", kcm_op_gen_new_send, NULL }, + { "INITIALIZE", kcm_op_initialize_send, kcm_op_initialize_recv }, + { "DESTROY", kcm_op_destroy_send, NULL }, + { "STORE", kcm_op_store_send, kcm_op_store_recv }, + { "RETRIEVE", NULL, NULL }, + { "GET_PRINCIPAL", kcm_op_get_principal_send, NULL }, + { "GET_CRED_UUID_LIST", kcm_op_get_cred_uuid_list_send, NULL }, + { "GET_CRED_BY_UUID", kcm_op_get_cred_by_uuid_send, kcm_op_get_cred_by_uuid_recv }, + { "REMOVE_CRED", kcm_op_remove_cred_send, NULL }, + { "SET_FLAGS", NULL, NULL }, + { "CHOWN", NULL, NULL }, + { "CHMOD", NULL, NULL }, + { "GET_INITIAL_TICKET", NULL, NULL }, + { "GET_TICKET", NULL, NULL }, + { "MOVE_CACHE", NULL, NULL }, + { "GET_CACHE_UUID_LIST", kcm_op_get_cache_uuid_list_send, NULL }, + { "GET_CACHE_BY_UUID", kcm_op_get_cache_by_uuid_send, NULL }, + { "GET_DEFAULT_CACHE", kcm_op_get_default_ccache_send, kcm_op_get_default_ccache_recv }, + { "SET_DEFAULT_CACHE", kcm_op_set_default_ccache_send, kcm_op_set_default_ccache_recv }, + { "GET_KDC_OFFSET", kcm_op_get_kdc_offset_send, NULL }, + { "SET_KDC_OFFSET", kcm_op_set_kdc_offset_send, kcm_op_set_kdc_offset_recv }, + { "ADD_NTLM_CRED", NULL, NULL }, + { "HAVE_NTLM_CRED", NULL, NULL }, + { "DEL_NTLM_CRED", NULL, NULL }, + { "DO_NTLM_AUTH", NULL, NULL }, + { "GET_NTLM_USER_LIST", NULL, NULL }, + + { NULL, NULL, NULL } +}; + +/* MIT EXTENSIONS, see private header src/include/kcm.h in krb5 sources */ +#define KCM_MIT_OFFSET 13001 +static struct kcm_op kcm_mit_optable[] = { + { "GET_CRED_LIST", kcm_op_get_cred_list_send, NULL }, + + { NULL, NULL, NULL } +}; + +struct kcm_op *kcm_get_opt(uint16_t opcode) +{ + struct kcm_op *table; + struct kcm_op *op; + size_t len; + + DEBUG(SSSDBG_TRACE_INTERNAL, + "The client requested operation %"PRIu16"\n", opcode); + + table = kcm_optable; + len = sizeof(kcm_optable) / sizeof(struct kcm_op); + if (opcode >= KCM_MIT_OFFSET) { + opcode -= KCM_MIT_OFFSET; + table = kcm_mit_optable; + len = sizeof(kcm_mit_optable) / sizeof(struct kcm_op); + } + + if (opcode >= len) { + return NULL; + } + + op = &table[opcode]; + if (op->fn_recv == NULL) { + op->fn_recv = kcm_op_common_recv; + } + + return op; +} + +const char *kcm_opt_name(struct kcm_op *op) +{ + if (op == NULL || op->name == NULL) { + return "Unknown operation"; + } + + return op->name; +} diff --git a/src/responder/kcm/kcmsrv_ops.h b/src/responder/kcm/kcmsrv_ops.h new file mode 100644 index 0000000..5d80352 --- /dev/null +++ b/src/responder/kcm/kcmsrv_ops.h @@ -0,0 +1,67 @@ +/* + SSSD + + KCM Server - private header file + + Copyright (C) Red Hat, 2016 + + 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/>. +*/ + +#ifndef __KCMSRV_OPS_H__ +#define __KCMSRV_OPS_H__ + +#include "config.h" + +#include <dhash.h> +#include <sys/types.h> +#include "util/sss_iobuf.h" +#include "responder/kcm/kcmsrv_pvt.h" + +/* The initial packet size, which can later be grown up to KCM_PACKET_MAX_SIZE. + * The initial size is a trade off that is expected to best serve most of the + * cases (typical credentials size). + */ +#define KCM_PACKET_INITIAL_SIZE 4096 + +/* The maximum length of a request or reply as defined by the RPC + * protocol. This is the same constant size as MIT KRB5 uses + * This limit comes from: + * https://github.com/krb5/krb5/blob/c20251dafd6120fa08c76b19315cb9deb1a1b24e/src/lib/krb5/ccache/cc_kcm.c#L54 + */ +#define KCM_PACKET_MAX_SIZE 10*1024*1024 + +struct kcm_op; +struct kcm_op *kcm_get_opt(uint16_t opcode); +const char *kcm_opt_name(struct kcm_op *op); + +struct kcm_conn_data { + /* Credentials obtained by GET_CRED_UUID_LIST. We use to improve performance + * by avoiding ccache lookups in GET_CRED_BY_UUID. */ + hash_table_t *creds; +}; + +struct tevent_req *kcm_cmd_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ops_queue_ctx *qctx, + struct kcm_resp_ctx *kcm_data, + struct kcm_conn_data *conn_data, + struct cli_creds *client, + struct kcm_data *input, + struct kcm_op *op); +errno_t kcm_cmd_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct sss_iobuf **_reply); + +#endif /* __KCMSRV_OPS_H__ */ diff --git a/src/responder/kcm/kcmsrv_pvt.h b/src/responder/kcm/kcmsrv_pvt.h new file mode 100644 index 0000000..0536a2c --- /dev/null +++ b/src/responder/kcm/kcmsrv_pvt.h @@ -0,0 +1,106 @@ +/* + SSSD + + KCM Server - private header file + + Copyright (C) Red Hat, 2016 + + 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/>. +*/ + +#ifndef __KCMSRV_PVT_H__ +#define __KCMSRV_PVT_H__ + +#include "config.h" + +#include <sys/types.h> +#include <krb5/krb5.h> +#include "responder/common/responder.h" + +#define KCM_PROTOCOL_VERSION_MAJOR 2 +#define KCM_PROTOCOL_VERSION_MINOR 0 + +/* This should ideally be in RUNSTATEDIR, but Heimdal uses a hardcoded + * /var/run, and we need to use the same default path. */ +#define DEFAULT_KCM_SOCKET_PATH "/var/run/.heim_org.h5l.kcm-socket" + +/* + * KCM IO structure + * + * In theory we cold use sss_iobuf there, but since iobuf was + * made opaque, this allows it to allocate the structures on + * the stack in one go. + * */ +struct kcm_data { + uint8_t *data; + size_t length; +}; + +/* + * To avoid leaking the sssd-specific responder data to other + * modules, the ccache databases and other KCM specific data + * are kept separately + */ +struct kcm_resp_ctx { + krb5_context k5c; + struct kcm_ccdb *db; +}; + +/* Supported ccache back ends */ +enum kcm_ccdb_be { + CCDB_BE_MEMORY, + CCDB_BE_SECDB, +}; + +/* + * responder context that contains both the responder data, + * like the ccaches and the sssd-specific stuff like the + * generic responder ctx + */ +struct kcm_ctx { + struct resp_ctx *rctx; + int fd_limit; + char *socket_path; + enum kcm_ccdb_be cc_be; + struct kcm_ops_queue_ctx *qctx; + + struct kcm_resp_ctx *kcm_data; +}; + +int kcm_connection_setup(struct cli_ctx *cctx); + +/* + * Internally in SSSD-KCM we use SSSD-internal error codes so that we + * can always the same sss_strerror() functions to format the errors + * nicely, but the client expects libkrb5 error codes. + */ +krb5_error_code sss2krb5_error(errno_t err); + +/* We enqueue all requests by the same UID to avoid concurrency issues. + */ +struct kcm_ops_queue_entry; + +struct kcm_ops_queue_ctx *kcm_ops_queue_create(TALLOC_CTX *mem_ctx, + struct kcm_ctx *kctx); + +struct tevent_req *kcm_op_queue_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct kcm_ops_queue_ctx *qctx, + struct cli_creds *client); + +errno_t kcm_op_queue_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct kcm_ops_queue_entry **_entry); + +#endif /* __KCMSRV_PVT_H__ */ diff --git a/src/responder/kcm/secrets/config.c b/src/responder/kcm/secrets/config.c new file mode 100644 index 0000000..84462b8 --- /dev/null +++ b/src/responder/kcm/secrets/config.c @@ -0,0 +1,92 @@ +/* + SSSD + + Local secrets database -- configuration + + Copyright (C) Red Hat 2018 + + 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 "util/util.h" +#include "secrets.h" + +errno_t sss_sec_get_quota(struct confdb_ctx *cdb, + const char *section_config_path, + struct sss_sec_quota_opt *dfl_max_containers_nest_level, + struct sss_sec_quota_opt *dfl_max_num_secrets, + struct sss_sec_quota_opt *dfl_max_num_uid_secrets, + struct sss_sec_quota_opt *dfl_max_payload, + struct sss_sec_quota *quota) +{ + int ret; + + if (cdb == NULL || section_config_path == NULL || quota == NULL) { + return EINVAL; + } + + ret = confdb_get_int(cdb, + section_config_path, + dfl_max_containers_nest_level->opt_name, + dfl_max_containers_nest_level->default_value, + "a->containers_nest_level); + + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to get container nesting level for %s\n", + section_config_path); + return ret; + } + + ret = confdb_get_int(cdb, + section_config_path, + dfl_max_num_secrets->opt_name, + dfl_max_num_secrets->default_value, + "a->max_secrets); + + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to get maximum number of entries for %s\n", + section_config_path); + return ret; + } + + ret = confdb_get_int(cdb, + section_config_path, + dfl_max_num_uid_secrets->opt_name, + dfl_max_num_uid_secrets->default_value, + "a->max_uid_secrets); + + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to get maximum number of per-UID entries for %s\n", + section_config_path); + return ret; + } + + ret = confdb_get_int(cdb, + section_config_path, + dfl_max_payload->opt_name, + dfl_max_payload->default_value, + "a->max_payload_size); + + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to get payload's maximum size for an entry in %s\n", + section_config_path); + return ret; + } + + return EOK; +} diff --git a/src/responder/kcm/secrets/sec_pvt.h b/src/responder/kcm/secrets/sec_pvt.h new file mode 100644 index 0000000..4118eaa --- /dev/null +++ b/src/responder/kcm/secrets/sec_pvt.h @@ -0,0 +1,47 @@ +/* + SSSD + + Local secrets database - private header + + Copyright (C) Red Hat 2018 + + 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/>. +*/ + +#ifndef __SECRETS_PVT_H_ +#define __SECRETS_PVT_H_ + +#include <errno.h> +#include <stdlib.h> +#include <stdio.h> +#include <sys/types.h> + +#include "secrets.h" + +struct sss_sec_ctx { + struct ldb_context *ldb; + + struct sss_sec_quota *quota_kcm; +}; + +struct sss_sec_req { + char *path; + const char *basedn; + struct ldb_dn *req_dn; + struct sss_sec_quota *quota; + + struct sss_sec_ctx *sctx; +}; + +#endif /* __SECRETS_PVT_H_ */ diff --git a/src/responder/kcm/secrets/secrets.c b/src/responder/kcm/secrets/secrets.c new file mode 100644 index 0000000..a37edcc --- /dev/null +++ b/src/responder/kcm/secrets/secrets.c @@ -0,0 +1,1229 @@ +/* + SSSD + + Local secrets database + + Copyright (C) Red Hat 2018 + + 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 "config.h" + +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <time.h> +#include <uuid/uuid.h> + +#include "responder/kcm/kcmsrv_ccache.h" +#include "util/util.h" +#include "util/util_creds.h" +#include "util/sss_iobuf.h" +#include "util/strtonum.h" +#include "util/crypto/sss_crypto.h" +#include "sec_pvt.h" +#include "secrets.h" + +#define KCM_PEER_UID 0 + +#define KCM_BASEDN "cn=kcm" + +#define LOCAL_CONTAINER_FILTER "(type=container)" +#define LOCAL_NON_CONTAINER_FILTER "(!"LOCAL_CONTAINER_FILTER")" + +#define SEC_ATTR_SECRET "secret" +#define SEC_ATTR_TYPE "type" +#define SEC_ATTR_CTIME "creationTime" + +static struct sss_sec_quota default_kcm_quota = { + .max_secrets = DEFAULT_SEC_KCM_MAX_SECRETS, + .max_uid_secrets = DEFAULT_SEC_KCM_MAX_UID_SECRETS, + .max_payload_size = DEFAULT_SEC_KCM_MAX_PAYLOAD_SIZE, + .containers_nest_level = DEFAULT_SEC_CONTAINERS_NEST_LEVEL, +}; + +static char *local_dn_to_path(TALLOC_CTX *mem_ctx, + struct ldb_dn *basedn, + struct ldb_dn *dn); + +static int local_db_check_containers(TALLOC_CTX *mem_ctx, + struct sss_sec_ctx *sec_ctx, + struct ldb_dn *leaf_dn) +{ + TALLOC_CTX *tmp_ctx; + static const char *attrs[] = { NULL}; + struct ldb_result *res = NULL; + struct ldb_dn *dn; + int num; + int ret; + + tmp_ctx = talloc_new(mem_ctx); + if (!tmp_ctx) return ENOMEM; + + dn = ldb_dn_copy(tmp_ctx, leaf_dn); + if (!dn) { + ret = ENOMEM; + goto done; + } + + /* We need to exclude the leaf as that will be the new child entry, + * We also do not care for the synthetic containers that constitute the + * base path (cn=<uidnumber>,cn=users,cn=secrets), so in total we remove + * 4 components */ + num = ldb_dn_get_comp_num(dn) - 4; + + for (int i = 0; i < num; i++) { + /* remove the child first (we do not want to check the leaf) */ + if (!ldb_dn_remove_child_components(dn, 1)) return EFAULT; + + /* and check the parent container exists */ + DEBUG(SSSDBG_TRACE_INTERNAL, + "Searching for [%s] at [%s] with scope=base\n", + LOCAL_CONTAINER_FILTER, ldb_dn_get_linearized(dn)); + + ret = ldb_search(sec_ctx->ldb, tmp_ctx, &res, dn, LDB_SCOPE_BASE, + attrs, LOCAL_CONTAINER_FILTER); + if (ret != LDB_SUCCESS || res->count != 1) { + DEBUG(SSSDBG_TRACE_LIBS, + "DN [%s] does not exist\n", ldb_dn_get_linearized(dn)); + ret = ENOENT; + goto done; + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static int local_db_check_number_of_secrets(TALLOC_CTX *mem_ctx, + struct sss_sec_req *req) +{ + TALLOC_CTX *tmp_ctx; + static const char *attrs[] = { NULL }; + struct ldb_result *res = NULL; + struct ldb_dn *dn; + int ret; + + if (req->quota->max_secrets == 0) { + return EOK; + } + + tmp_ctx = talloc_new(mem_ctx); + if (!tmp_ctx) return ENOMEM; + + dn = ldb_dn_new(tmp_ctx, req->sctx->ldb, req->basedn); + if (!dn) { + ret = ENOMEM; + goto done; + } + + ret = ldb_search(req->sctx->ldb, tmp_ctx, &res, dn, LDB_SCOPE_SUBTREE, + attrs, LOCAL_NON_CONTAINER_FILTER); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_LIBS, + "ldb_search returned %d: %s\n", ret, ldb_strerror(ret)); + goto done; + } + + if (res->count >= req->quota->max_secrets) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot store any more secrets as the maximum allowed limit (%d) " + "has been reached\n", req->quota->max_secrets); + ret = ERR_SEC_INVALID_TOO_MANY_SECRETS; + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static struct ldb_dn *per_uid_container(TALLOC_CTX *mem_ctx, + struct ldb_dn *req_dn) +{ + int user_comp; + int num_comp; + struct ldb_dn *uid_base_dn; + + uid_base_dn = ldb_dn_copy(mem_ctx, req_dn); + if (uid_base_dn == NULL) { + return NULL; + } + + /* Remove all the components up to the per-user base path which consists + * of three components: + * cn=<uidnumber>,cn=users,cn=secrets + */ + user_comp = ldb_dn_get_comp_num(uid_base_dn) - 3; + + if (!ldb_dn_remove_child_components(uid_base_dn, user_comp)) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot remove child components\n"); + talloc_free(uid_base_dn); + return NULL; + } + + num_comp = ldb_dn_get_comp_num(uid_base_dn); + if (num_comp != 3) { + DEBUG(SSSDBG_OP_FAILURE, "Expected 3 components got %d\n", num_comp); + talloc_free(uid_base_dn); + return NULL; + } + + return uid_base_dn; +} + +static errno_t get_secret_expiration_time(uint8_t *key, size_t key_length, + uint8_t *sec, size_t sec_length, + time_t *_expiration) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx; + time_t expiration = 0; + struct cli_creds client = {}; + struct kcm_ccache *cc; + struct sss_iobuf *iobuf; + krb5_creds **cred_list, **cred; + const char *key_str; + + if (_expiration == NULL) { + return EINVAL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + key_str = talloc_strndup(tmp_ctx, (const char *) key, key_length); + if (key_str == NULL) { + ret = ENOMEM; + goto done; + } + + iobuf = sss_iobuf_init_readonly(tmp_ctx, sec, sec_length); + if (iobuf == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sec_kv_to_ccache_binary(tmp_ctx, key_str, iobuf, &client, &cc); + if (ret != EOK) { + goto done; + } + + cred_list = kcm_cc_unmarshal(tmp_ctx, NULL, cc); + if (cred_list == NULL) { + ret = ENOMEM; + goto done; + } + + for (cred = cred_list; *cred != NULL; cred++) { + if ((*cred)->times.endtime != 0) { + expiration = (time_t) (*cred)->times.endtime; + break; + } + } + + *_expiration = expiration; + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t local_db_remove_oldest_expired_secret(struct ldb_result *res, + struct sss_sec_req *req) +{ + struct sss_sec_req *new_req = NULL; + const struct ldb_val *val; + const struct ldb_val *rdn; + struct ldb_message *msg; + struct ldb_message_element *elem; + struct ldb_dn *basedn; + struct ldb_dn *oldest_dn = NULL; + time_t oldest_time = time(NULL); + time_t expiration; + unsigned int i; + int ret; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Removing the oldest expired credential\n"); + /* Between all the messages in result, there is also the key we are + * currently treating, but because yet it doesn't have an expiration time, + * it will be skipped. + */ + for (i = 0; i < res->count; i++) { + msg = res->msgs[i]; + + /* Skip cn=default,... or any non cn=... */ + rdn = ldb_dn_get_rdn_val(msg->dn); + if (strcmp(ldb_dn_get_rdn_name(msg->dn), "cn") != 0 + || strncmp("default", (char *) rdn->data, rdn->length) == 0) { + continue; + } + + elem = ldb_msg_find_element(msg, SEC_ATTR_SECRET); + if (elem != NULL) { + if (elem->num_values != 1) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Element %s has %u values. Ignoring it.\n", + SEC_ATTR_SECRET, elem->num_values); + ret = ERR_MALFORMED_ENTRY; + goto done; + } + + val = &elem->values[0]; + ret = get_secret_expiration_time(rdn->data, rdn->length, + val->data, val->length, + &expiration); + if (ret != EOK) { + goto done; + } + if (expiration > 0 && expiration < oldest_time) { + oldest_dn = msg->dn; + oldest_time = expiration; + } + } + } + + if (oldest_dn == NULL) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Found no expired credential to remove\n"); + ret = ERR_NO_MATCHING_CREDS; + goto done; + } + + new_req = talloc_zero(NULL, struct sss_sec_req); + if (new_req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to allocate the new request\n"); + ret = ENOMEM; + goto done; + } + + basedn = ldb_dn_new(new_req, req->sctx->ldb, req->basedn); + if (basedn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create a dn: %s\n", req->basedn); + ret = EINVAL; + goto done; + } + + new_req->basedn = req->basedn; + new_req->quota = req->quota; + new_req->req_dn = oldest_dn; + new_req->sctx = req->sctx; + new_req->path = local_dn_to_path(new_req, basedn, oldest_dn); + if (new_req->path == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create the path\n"); + ret = EINVAL; + goto done; + } + + ret = sss_sec_delete(new_req); + +done: + if (new_req != NULL) + talloc_free(new_req); + + return ret; +} + + +static int local_db_check_peruid_number_of_secrets(TALLOC_CTX *mem_ctx, + struct sss_sec_req *req) +{ + TALLOC_CTX *tmp_ctx; + static const char *attrs[] = { SEC_ATTR_SECRET, NULL }; + struct ldb_result *res = NULL; + struct ldb_dn *cli_basedn = NULL; + int ret; + + if (req->quota->max_uid_secrets == 0) { + return EOK; + } + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + cli_basedn = per_uid_container(tmp_ctx, req->req_dn); + if (cli_basedn == NULL) { + ret = ENOMEM; + goto done; + } + + ret = ldb_search(req->sctx->ldb, tmp_ctx, &res, cli_basedn, LDB_SCOPE_SUBTREE, + attrs, LOCAL_NON_CONTAINER_FILTER); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_LIBS, + "ldb_search returned %d: %s\n", ret, ldb_strerror(ret)); + goto done; + } + + if (res->count >= req->quota->max_uid_secrets) { + /* We reached the limit. Let's try to removed the + * oldest expired credential to free some space. */ + ret = local_db_remove_oldest_expired_secret(res, req); + if (ret != EOK) { + if (ret == ERR_NO_MATCHING_CREDS) { + /* max_uid_secrets is incremented for internal entries. */ + DEBUG(SSSDBG_OP_FAILURE, + "Cannot store any more secrets for this client (basedn %s) " + "as the maximum allowed limit (%d) has been reached\n", + ldb_dn_get_linearized(cli_basedn), + req->quota->max_uid_secrets - KCM_MAX_UID_EXTRA_SECRETS); + ret = ERR_SEC_INVALID_TOO_MANY_SECRETS; + } + goto done; + } + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +static int local_check_max_payload_size(struct sss_sec_req *req, + int payload_size) +{ + int max_payload_size; + + if (req->quota->max_payload_size == 0) { + return EOK; + } + + max_payload_size = req->quota->max_payload_size * 1024; /* KiB */ + if (payload_size > max_payload_size) { + DEBUG(SSSDBG_OP_FAILURE, + "Secrets' payload size [%d KiB (%d B)] exceeds the maximum " + "allowed payload size [%d KiB (%d B)]\n", + payload_size / 1024, /* KiB */ + payload_size, + req->quota->max_payload_size, /* KiB */ + max_payload_size); + + return ERR_SEC_PAYLOAD_SIZE_IS_TOO_LARGE; + } + + return EOK; +} + +static int local_db_check_containers_nest_level(struct sss_sec_req *req, + struct ldb_dn *leaf_dn) +{ + int nest_level; + + if (req->quota->containers_nest_level == 0) { + return EOK; + } + + /* We need do not care for the synthetic containers that constitute the + * base path (cn=<uidnumber>,cn=user,cn=secrets). */ + nest_level = ldb_dn_get_comp_num(leaf_dn) - 3; + if (nest_level > req->quota->containers_nest_level) { + DEBUG(SSSDBG_OP_FAILURE, + "Cannot create a nested container of depth %d as the maximum" + "allowed number of nested containers is %d.\n", + nest_level, req->quota->containers_nest_level); + + return ERR_SEC_INVALID_CONTAINERS_NEST_LEVEL; + } + + return EOK; +} + +static int local_db_create(struct sss_sec_req *req) +{ + struct ldb_message *msg; + int ret; + + DEBUG(SSSDBG_TRACE_FUNC, "Creating a container at [%s]\n", req->path); + + msg = ldb_msg_new(req); + if (!msg) { + ret = ENOMEM; + goto done; + } + msg->dn = req->req_dn; + + /* make sure containers exist */ + ret = local_db_check_containers(msg, req->sctx, msg->dn); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "local_db_check_containers failed for [%s]: [%d]: %s\n", + ldb_dn_get_linearized(msg->dn), ret, sss_strerror(ret)); + goto done; + } + + ret = local_db_check_containers_nest_level(req, msg->dn); + if (ret != EOK) goto done; + + ret = ldb_msg_add_string(msg, SEC_ATTR_TYPE, "container"); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "ldb_msg_add_string failed adding type:container [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = ldb_msg_add_fmt(msg, SEC_ATTR_CTIME, "%lu", time(NULL)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "ldb_msg_add_string failed adding creationTime [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = ldb_add(req->sctx->ldb, msg); + if (ret != LDB_SUCCESS) { + if (ret == LDB_ERR_ENTRY_ALREADY_EXISTS) { + DEBUG(SSSDBG_FUNC_DATA, + "Secret %s already exists\n", ldb_dn_get_linearized(msg->dn)); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to add secret [%s]: [%d]: %s\n", + ldb_dn_get_linearized(msg->dn), ret, ldb_strerror(ret)); + } + ret = sss_ldb_error_to_errno (ret); + goto done; + } + + ret = EOK; + +done: + talloc_free(msg); + return ret; +} + +/* non static since it is used in test_kcm_renewals.c */ +errno_t sss_sec_init_with_path(TALLOC_CTX *mem_ctx, + struct sss_sec_quota *quota, + const char *dbpath, + struct sss_sec_ctx **_sec_ctx) +{ + struct sss_sec_ctx *sec_ctx; + TALLOC_CTX *tmp_ctx; + errno_t ret; + + if (_sec_ctx == NULL) { + return EINVAL; + } + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + sec_ctx = talloc_zero(tmp_ctx, struct sss_sec_ctx); + if (sec_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + if (quota) { + sec_ctx->quota_kcm = quota; + } else { + DEBUG(SSSDBG_TRACE_LIBS, "No custom quota set, using defaults\n"); + sec_ctx->quota_kcm = &default_kcm_quota; + } + + sec_ctx->ldb = ldb_init(sec_ctx, NULL); + if (sec_ctx->ldb == NULL) { + ret = ENOMEM; + goto done; + } + + ret = ldb_connect(sec_ctx->ldb, dbpath, 0, NULL); + if (ret != LDB_SUCCESS) { + DEBUG(SSSDBG_TRACE_LIBS, + "ldb_connect(%s) returned %d: %s\n", + dbpath, ret, ldb_strerror(ret)); + talloc_free(sec_ctx->ldb); + ret = EIO; + goto done; + } + + ret = EOK; + *_sec_ctx = talloc_steal(mem_ctx, sec_ctx); +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t sss_sec_init(TALLOC_CTX *mem_ctx, + struct sss_sec_quota *quota, + struct sss_sec_ctx **_sec_ctx) +{ + const char *dbpath = SECRETS_DB_PATH"/secrets.ldb"; + errno_t ret; + + ret = sss_sec_init_with_path(mem_ctx, quota, dbpath, _sec_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to initialize secdb [%d]: %s\n", + ret, sss_strerror(ret)); + ret = EIO; + goto done; + } + + ret = EOK; +done: + return ret; +} + +static int local_db_dn(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb, + const char *basedn, + const char *req_path, + struct ldb_dn **req_dn) +{ + struct ldb_dn *dn; + const char *s, *e; + int ret; + + dn = ldb_dn_new(mem_ctx, ldb, basedn); + if (!dn) { + ret = ENOMEM; + goto done; + } + + s = req_path; + + while (s && *s) { + e = strchr(s, '/'); + if (e) { + if (e == s) { + s++; + continue; + } + if (!ldb_dn_add_child_fmt(dn, "cn=%.*s", (int)(e - s), s)) { + ret = ENOMEM; + goto done; + } + s = e + 1; + } else { + if (!ldb_dn_add_child_fmt(dn, "cn=%s", s)) { + ret = ENOMEM; + goto done; + } + s = NULL; + } + } + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Local path for [%s] is [%s]\n", + req_path, ldb_dn_get_linearized(dn)); + *req_dn = dn; + ret = EOK; + +done: + return ret; +} + +errno_t sss_sec_new_req(TALLOC_CTX *mem_ctx, + struct sss_sec_ctx *sec_ctx, + const char *url, + uid_t client, + struct sss_sec_req **_req) +{ + struct sss_sec_req *req; + TALLOC_CTX *tmp_ctx; + errno_t ret; + + if (sec_ctx == NULL || url == NULL || _req == NULL) { + return EINVAL; + } + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + req = talloc_zero(tmp_ctx, struct sss_sec_req); + if (req == NULL) { + ret = ENOMEM; + goto done; + } + req->sctx = sec_ctx; + + /* drop the prefix and select a basedn instead */ + if (geteuid() != KCM_PEER_UID && client != KCM_PEER_UID) { + DEBUG(SSSDBG_CRIT_FAILURE, + "UID %"SPRIuid" is not allowed to access the KCM hive\n", + client); + ret = EPERM; + goto done; + } + + req->basedn = KCM_BASEDN; + req->quota = sec_ctx->quota_kcm; + req->path = talloc_strdup(req, url); + if (req->path == NULL) { + ret = ENOMEM; + goto done; + } + + ret = local_db_dn(req, sec_ctx->ldb, req->basedn, req->path, &req->req_dn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to map request to local db DN\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_LIBS, "Local DB path is %s\n", req->path); + + ret = EOK; + *_req = talloc_steal(mem_ctx, req); +done: + talloc_free(tmp_ctx); + return ret; +} + +static char *local_dn_to_path(TALLOC_CTX *mem_ctx, + struct ldb_dn *basedn, + struct ldb_dn *dn) +{ + int basecomps; + int dncomps; + char *path = NULL; + + basecomps = ldb_dn_get_comp_num(basedn); + dncomps = ldb_dn_get_comp_num(dn); + + for (int i = dncomps - basecomps; i > 0; i--) { + const struct ldb_val *val; + + val = ldb_dn_get_component_val(dn, i - 1); + if (!val) return NULL; + + if (path) { + path = talloc_strdup_append_buffer(path, "/"); + if (!path) return NULL; + path = talloc_strndup_append_buffer(path, (char *)val->data, + val->length); + } else { + path = talloc_strndup(mem_ctx, (char *)val->data, val->length); + } + if (!path) return NULL; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Secrets path for [%s] is [%s]\n", + ldb_dn_get_linearized(dn), path); + return path; +} + +/* Complete list of ccache names(UUID:name) */ +errno_t sss_sec_list_cc_uuids(TALLOC_CTX *mem_ctx, + struct sss_sec_ctx *sec, + const char ***_uuid_list, + const char ***_uid_list, + size_t *_uuid_list_count) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_result *res; + struct ldb_dn *dn; + const struct ldb_val *name_val; + const struct ldb_val *uid_val; + static const char *attrs[] = { "distinguishedName", NULL }; + const char **uuid_list; + const char **uid_list; + size_t real_count = 0; + int ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + dn = ldb_dn_new(tmp_ctx, sec->ldb, "cn=persistent,cn=kcm"); + + ret = ldb_search(sec->ldb, tmp_ctx, &res, dn, LDB_SCOPE_SUBTREE, + attrs, LOCAL_NON_CONTAINER_FILTER); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_LIBS, + "ldb_search returned [%d]: %s\n", ret, ldb_strerror(ret)); + ret = EIO; + goto done; + } + + if (res->count == 0) { + DEBUG(SSSDBG_TRACE_LIBS, "No ccaches found\n"); + ret = ENOENT; + goto done; + } + + uuid_list = talloc_zero_array(tmp_ctx, const char *, res->count); + if (uuid_list == NULL) { + ret = ENOMEM; + goto done; + } + + uid_list = talloc_zero_array(tmp_ctx, const char *, res->count); + if (uid_list == NULL) { + ret = ENOMEM; + goto done; + } + + for (int i = 0; i < res->count; i++) { + name_val = ldb_dn_get_component_val(res->msgs[i]->dn, 0); + uid_val = ldb_dn_get_component_val(res->msgs[i]->dn, 2); + if (strcmp((const char *)name_val->data, "default") == 0) { + continue; + } + + uuid_list[real_count] = talloc_strdup(uuid_list, (const char *)name_val->data); + if (uuid_list[real_count] == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to allocate UUID\n"); + ret = ENOMEM; + goto done; + } + + uid_list[real_count] = talloc_strdup(uid_list, (const char *)uid_val->data); + if (uid_list[real_count] == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to allocate uid\n"); + ret = ENOMEM; + goto done; + } + + real_count++; + } + + *_uid_list = talloc_steal(mem_ctx, uid_list); + *_uuid_list = talloc_steal(mem_ctx, uuid_list); + *_uuid_list_count = real_count; + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t sss_sec_list(TALLOC_CTX *mem_ctx, + struct sss_sec_req *req, + char ***_keys, + size_t *_num_keys) +{ + TALLOC_CTX *tmp_ctx; + static const char *attrs[] = { SEC_ATTR_SECRET, NULL }; + struct ldb_result *res; + char **keys; + int ret; + + if (req == NULL || _keys == NULL || _num_keys == NULL) { + return EINVAL; + } + + tmp_ctx = talloc_new(mem_ctx); + if (!tmp_ctx) return ENOMEM; + + DEBUG(SSSDBG_TRACE_FUNC, "Listing keys at [%s]\n", req->path); + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Searching at [%s] with scope=subtree\n", + ldb_dn_get_linearized(req->req_dn)); + + ret = ldb_search(req->sctx->ldb, tmp_ctx, &res, req->req_dn, LDB_SCOPE_SUBTREE, + attrs, LOCAL_NON_CONTAINER_FILTER); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_LIBS, + "ldb_search returned [%d]: %s\n", ret, ldb_strerror(ret)); + ret = ENOENT; + goto done; + } + + if (res->count == 0) { + DEBUG(SSSDBG_TRACE_LIBS, "No secrets found\n"); + ret = ENOENT; + goto done; + } + + keys = talloc_array(mem_ctx, char *, res->count); + if (!keys) { + ret = ENOMEM; + goto done; + } + + for (unsigned i = 0; i < res->count; i++) { + keys[i] = local_dn_to_path(keys, req->req_dn, res->msgs[i]->dn); + if (!keys[i]) { + ret = ENOMEM; + goto done; + } + } + + *_keys = keys; + DEBUG(SSSDBG_TRACE_LIBS, "Returning %d secrets\n", res->count); + *_num_keys = res->count; + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t sss_sec_get(TALLOC_CTX *mem_ctx, + struct sss_sec_req *req, + uint8_t **_secret, + size_t *_secret_len) +{ + TALLOC_CTX *tmp_ctx; + static const char *attrs[] = { SEC_ATTR_SECRET, NULL }; + struct ldb_result *res; + const struct ldb_val *attr_secret; + int ret; + + if (req == NULL || _secret == NULL) { + return EINVAL; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Retrieving a secret from [%s]\n", req->path); + + tmp_ctx = talloc_new(mem_ctx); + if (!tmp_ctx) return ENOMEM; + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Searching at [%s] with scope=base\n", + ldb_dn_get_linearized(req->req_dn)); + + ret = ldb_search(req->sctx->ldb, tmp_ctx, &res, req->req_dn, LDB_SCOPE_BASE, + attrs, LOCAL_NON_CONTAINER_FILTER); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_LIBS, + "ldb_search returned [%d]: %s\n", ret, ldb_strerror(ret)); + ret = ENOENT; + goto done; + } + + switch (res->count) { + case 0: + DEBUG(SSSDBG_TRACE_LIBS, "No secret found\n"); + ret = ENOENT; + goto done; + case 1: + break; + default: + DEBUG(SSSDBG_OP_FAILURE, + "Too many secrets returned with BASE search\n"); + ret = E2BIG; + goto done; + } + + attr_secret = ldb_msg_find_ldb_val(res->msgs[0], SEC_ATTR_SECRET); + if ((!attr_secret) || (attr_secret->length == 0)) { + DEBUG(SSSDBG_CRIT_FAILURE, "The 'secret' attribute is missing\n"); + ret = ENOENT; + goto done; + } + + *_secret = talloc_memdup(mem_ctx, attr_secret->data, attr_secret->length); + if (*_secret == NULL) { + ret = ENOMEM; + goto done; + } + + if (_secret_len) { + *_secret_len = attr_secret->length; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t sss_sec_put(struct sss_sec_req *req, + uint8_t *secret, + size_t secret_len) +{ + struct ldb_message *msg; + struct ldb_val secret_val; + int ret; + + if (req == NULL || secret == NULL) { + return EINVAL; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Adding a secret to [%s]\n", req->path); + + msg = ldb_msg_new(req); + if (!msg) { + ret = ENOMEM; + goto done; + } + msg->dn = req->req_dn; + + /* make sure containers exist */ + ret = local_db_check_containers(msg, req->sctx, msg->dn); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "local_db_check_containers failed for [%s]: [%d]: %s\n", + ldb_dn_get_linearized(msg->dn), ret, sss_strerror(ret)); + goto done; + } + + ret = local_db_check_peruid_number_of_secrets(msg, req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "local_db_check_peruid_number_of_secrets failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = local_db_check_number_of_secrets(msg, req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "local_db_check_number_of_secrets failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = local_check_max_payload_size(req, secret_len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "local_check_max_payload_size failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + secret_val.length = secret_len; + secret_val.data = talloc_memdup(req->sctx, secret, secret_len); + if (!secret_val.data) { + ret = ENOMEM; + goto done; + } + + ret = ldb_msg_add_value(msg, SEC_ATTR_SECRET, &secret_val, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "ldb_msg_add_string failed adding secret [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = ldb_msg_add_fmt(msg, SEC_ATTR_CTIME, "%lu", time(NULL)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "ldb_msg_add_string failed adding creationTime [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = ldb_add(req->sctx->ldb, msg); + if (ret != LDB_SUCCESS) { + if (ret == LDB_ERR_ENTRY_ALREADY_EXISTS) { + DEBUG(SSSDBG_OP_FAILURE, + "Secret %s already exists\n", ldb_dn_get_linearized(msg->dn)); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to add secret [%s]: [%d]: %s\n", + ldb_dn_get_linearized(msg->dn), ret, ldb_strerror(ret)); + } + ret = sss_ldb_error_to_errno (ret); + goto done; + } + + ret = EOK; +done: + talloc_free(msg); + return ret; +} + +errno_t sss_sec_update(struct sss_sec_req *req, + uint8_t *secret, + size_t secret_len) +{ + struct ldb_message *msg; + struct ldb_val secret_val; + int ret; + + if (req == NULL || secret == NULL) { + return EINVAL; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Adding a secret to [%s]\n", req->path); + + msg = ldb_msg_new(req); + if (!msg) { + ret = ENOMEM; + goto done; + } + msg->dn = req->req_dn; + + /* make sure containers exist */ + ret = local_db_check_containers(msg, req->sctx, msg->dn); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "local_db_check_containers failed for [%s]: [%d]: %s\n", + ldb_dn_get_linearized(msg->dn), ret, sss_strerror(ret)); + goto done; + } + + ret = local_db_check_peruid_number_of_secrets(msg, req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "local_db_check_peruid_number_of_secrets failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = local_db_check_number_of_secrets(msg, req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "local_db_check_number_of_secrets failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = local_check_max_payload_size(req, secret_len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "local_check_max_payload_size failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + secret_val.length = secret_len; + secret_val.data = talloc_memdup(req->sctx, secret, secret_len); + if (!secret_val.data) { + ret = ENOMEM; + goto done; + } + + /* FIXME - should we have a lastUpdate timestamp? */ + ret = ldb_msg_add_empty(msg, SEC_ATTR_SECRET, LDB_FLAG_MOD_REPLACE, NULL); + if (ret != LDB_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, + "ldb_msg_add_empty failed: [%s]\n", ldb_strerror(ret)); + ret = EIO; + goto done; + } + + ret = ldb_msg_add_value(msg, SEC_ATTR_SECRET, &secret_val, NULL); + if (ret != LDB_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, + "ldb_msg_add_string failed: [%s]\n", ldb_strerror(ret)); + ret = EIO; + goto done; + } + + ret = ldb_modify(req->sctx->ldb, msg); + if (ret == LDB_ERR_NO_SUCH_OBJECT) { + DEBUG(SSSDBG_MINOR_FAILURE, "No such object to modify\n"); + ret = sss_ldb_error_to_errno (ret); + goto done; + } else if (ret != LDB_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, + "ldb_modify failed: [%s](%d)[%s]\n", + ldb_strerror(ret), ret, ldb_errstring(req->sctx->ldb)); + ret = sss_ldb_error_to_errno (ret); + goto done; + } + + ret = EOK; +done: + talloc_free(msg); + return ret; +} + +errno_t sss_sec_delete(struct sss_sec_req *req) +{ + TALLOC_CTX *tmp_ctx; + static const char *attrs[] = { NULL }; + struct ldb_result *res; + int ret; + + if (req == NULL) { + return EINVAL; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Removing a secret from [%s]\n", req->path); + + tmp_ctx = talloc_new(req); + if (!tmp_ctx) return ENOMEM; + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Searching for [%s] at [%s] with scope=base\n", + LOCAL_CONTAINER_FILTER, ldb_dn_get_linearized(req->req_dn)); + + ret = ldb_search(req->sctx->ldb, tmp_ctx, &res, req->req_dn, LDB_SCOPE_BASE, + attrs, LOCAL_CONTAINER_FILTER); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_LIBS, + "ldb_search returned %d: %s\n", ret, ldb_strerror(ret)); + goto done; + } + + if (res->count == 1) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Searching for children of [%s]\n", ldb_dn_get_linearized(req->req_dn)); + ret = ldb_search(req->sctx->ldb, tmp_ctx, &res, req->req_dn, LDB_SCOPE_ONELEVEL, + attrs, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_LIBS, + "ldb_search returned %d: %s\n", ret, ldb_strerror(ret)); + goto done; + } + + if (res->count > 0) { + ret = EEXIST; + DEBUG(SSSDBG_OP_FAILURE, + "Failed to remove '%s': Container is not empty\n", + ldb_dn_get_linearized(req->req_dn)); + + goto done; + } + } + + ret = ldb_delete(req->sctx->ldb, req->req_dn); + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_LIBS, + "ldb_delete returned %d: %s\n", ret, ldb_strerror(ret)); + /* fall through */ + } + + if (ret != LDB_SUCCESS && ret != LDB_ERR_NO_SUCH_OBJECT) { + DEBUG(SSSDBG_CRIT_FAILURE, + "LDB returned unexpected error: [%s]\n", + ldb_strerror(ret)); + } + ret = sss_ldb_error_to_errno (ret); + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t sss_sec_create_container(struct sss_sec_req *req) +{ + int plen; + + if (req == NULL) { + return EINVAL; + } + + plen = strlen(req->path); + + if (req->path[plen - 1] != '/') { + return EINVAL; + } + + req->path[plen - 1] = '\0'; + return local_db_create(req); +} diff --git a/src/responder/kcm/secrets/secrets.h b/src/responder/kcm/secrets/secrets.h new file mode 100644 index 0000000..537f6c9 --- /dev/null +++ b/src/responder/kcm/secrets/secrets.h @@ -0,0 +1,114 @@ +/* + SSSD + + Local secrets database + + Copyright (C) Red Hat 2018 + + 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/>. +*/ + +#ifndef __SECRETS_H_ +#define __SECRETS_H_ + +#include <errno.h> +#include <stdlib.h> +#include <stdbool.h> +#include <talloc.h> +#include <uuid/uuid.h> + +#include "confdb/confdb.h" + +#define DEFAULT_SEC_CONTAINERS_NEST_LEVEL 4 + +/* The number of secrets in the /kcm hive should be quite small, + * but the secret size must be large because one secret in the /kcm + * hive holds the whole ccache which consists of several credentials + */ +#define DEFAULT_SEC_KCM_MAX_SECRETS 0 /* unlimited */ +#define DEFAULT_SEC_KCM_MAX_UID_SECRETS 64 +#define DEFAULT_SEC_KCM_MAX_PAYLOAD_SIZE 65536 + +/* Even cn=default is considered a secret that adds up to + * the quota. To avoid off-by-one-confusion, increase + * the quota by two to 1) account for the cn=default object + * and 2) always allow writing to cn=defaults even if we + * are exactly at the quota limit + */ +#define KCM_MAX_UID_EXTRA_SECRETS 2 + +struct sss_sec_ctx; + +struct sss_sec_req; + +struct sss_sec_quota_opt { + const char *opt_name; + int default_value; +}; + +struct sss_sec_quota { + int max_secrets; + int max_uid_secrets; + int max_payload_size; + int containers_nest_level; +}; + +errno_t sss_sec_init(TALLOC_CTX *mem_ctx, + struct sss_sec_quota *quota, + struct sss_sec_ctx **_sec_ctx); + +errno_t sss_sec_new_req(TALLOC_CTX *mem_ctx, + struct sss_sec_ctx *sec_ctx, + const char *url, + uid_t client, + struct sss_sec_req **_req); + +errno_t sss_sec_delete(struct sss_sec_req *req); + +errno_t sss_sec_list_cc_uuids(TALLOC_CTX *mem_ctx, + struct sss_sec_ctx *sec_ctx, + const char ***_uuid_list, + const char ***_uid_list, + size_t *uuid_list_count); + +errno_t sss_sec_list(TALLOC_CTX *mem_ctx, + struct sss_sec_req *req, + char ***_keys, + size_t *num_keys); + +errno_t sss_sec_get(TALLOC_CTX *mem_ctx, + struct sss_sec_req *req, + uint8_t **_secret, + size_t *_secret_len); + +errno_t sss_sec_put(struct sss_sec_req *req, + uint8_t *secret, + size_t secret_len); + +errno_t sss_sec_update(struct sss_sec_req *req, + uint8_t *secret, + size_t secret_len); + +errno_t sss_sec_create_container(struct sss_sec_req *req); + + +errno_t sss_sec_get_quota(struct confdb_ctx *cdb, + const char *section_config_path, + struct sss_sec_quota_opt *dfl_max_containers_nest_level, + struct sss_sec_quota_opt *dfl_max_num_secrets, + struct sss_sec_quota_opt *dfl_max_num_uid_secrets, + struct sss_sec_quota_opt *dfl_max_payload, + struct sss_sec_quota *quota); + +#endif /* __SECRETS_H_ */ diff --git a/src/responder/nss/nss_cmd.c b/src/responder/nss/nss_cmd.c new file mode 100644 index 0000000..dd80113 --- /dev/null +++ b/src/responder/nss/nss_cmd.c @@ -0,0 +1,1439 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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 <tevent.h> +#include <talloc.h> + +#include "util/util.h" +#include "util/sss_ptr_hash.h" +#include "db/sysdb.h" +#include "responder/nss/nss_private.h" +#include "responder/nss/nss_protocol.h" + +static struct sss_nss_cmd_ctx * +sss_nss_cmd_ctx_create(TALLOC_CTX *mem_ctx, + struct cli_ctx *cli_ctx, + enum cache_req_type type, + sss_nss_protocol_fill_packet_fn fill_fn) +{ + struct sss_nss_cmd_ctx *cmd_ctx; + + cmd_ctx = talloc_zero(mem_ctx, struct sss_nss_cmd_ctx); + if (cmd_ctx == NULL) { + return NULL; + } + + cmd_ctx->cli_ctx = cli_ctx; + cmd_ctx->nss_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct sss_nss_ctx); + cmd_ctx->state_ctx = talloc_get_type(cli_ctx->state_ctx, + struct sss_nss_state_ctx); + cmd_ctx->type = type; + cmd_ctx->fill_fn = fill_fn; + + return cmd_ctx; +} + +static errno_t eval_flags(struct sss_nss_cmd_ctx *cmd_ctx, + struct cache_req_data *data) +{ + if ((cmd_ctx->flags & SSS_NSS_EX_FLAG_NO_CACHE) != 0 + && (cmd_ctx->flags & SSS_NSS_EX_FLAG_INVALIDATE_CACHE) != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Flags SSS_NSS_EX_FLAG_NO_CACHE and " + "SSS_NSS_EX_FLAG_INVALIDATE_CACHE are " + "mutually exclusive.\n"); + return EINVAL; + } + + if ((cmd_ctx->flags & SSS_NSS_EX_FLAG_NO_CACHE) != 0) { + cache_req_data_set_bypass_cache(data, true); + } else if ((cmd_ctx->flags & SSS_NSS_EX_FLAG_INVALIDATE_CACHE) != 0) { + cache_req_data_set_bypass_dp(data, true); + } + + return EOK; +} + +static void sss_nss_getby_done(struct tevent_req *subreq); +static void sss_nss_getlistby_done(struct tevent_req *subreq); + +static errno_t sss_nss_getby_name(struct cli_ctx *cli_ctx, + bool ex_version, + enum cache_req_type type, + const char **attrs, + enum sss_mc_type memcache, + sss_nss_protocol_fill_packet_fn fill_fn) +{ + struct cache_req_data *data; + struct sss_nss_cmd_ctx *cmd_ctx; + struct tevent_req *subreq; + const char *rawname; + errno_t ret; + + cmd_ctx = sss_nss_cmd_ctx_create(cli_ctx, cli_ctx, type, fill_fn); + if (cmd_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + cmd_ctx->flags = 0; + if (ex_version) { + ret = sss_nss_protocol_parse_name_ex(cli_ctx, &rawname, &cmd_ctx->flags); + } else { + ret = sss_nss_protocol_parse_name(cli_ctx, &rawname); + } + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid request message!\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Input name: %s\n", rawname); + + data = cache_req_data_name_attrs(cmd_ctx, type, rawname, attrs); + if (data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to set cache request data!\n"); + ret = ENOMEM; + goto done; + } + + ret = eval_flags(cmd_ctx, data); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "eval_flags failed.\n"); + goto done; + } + + subreq = sss_nss_get_object_send(cmd_ctx, cli_ctx->ev, cli_ctx, + data, memcache, rawname, 0); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_nss_get_object_send() failed\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sss_nss_getby_done, cmd_ctx); + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(cmd_ctx); + return sss_nss_protocol_done(cli_ctx, ret); + } + + return EOK; +} + +static errno_t sss_nss_getby_id(struct cli_ctx *cli_ctx, + bool ex_version, + enum cache_req_type type, + const char **attrs, + enum sss_mc_type memcache, + sss_nss_protocol_fill_packet_fn fill_fn) +{ + struct cache_req_data *data; + struct sss_nss_cmd_ctx *cmd_ctx; + struct tevent_req *subreq; + uint32_t id; + errno_t ret; + + cmd_ctx = sss_nss_cmd_ctx_create(cli_ctx, cli_ctx, type, fill_fn); + if (cmd_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + if (ex_version) { + ret = sss_nss_protocol_parse_id_ex(cli_ctx, &id, &cmd_ctx->flags); + } else { + ret = sss_nss_protocol_parse_id(cli_ctx, &id); + } + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid request message!\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Input ID: %u (looking up '%s')\n", id, + (fill_fn == sss_nss_protocol_fill_sid) ? "SID" : "POSIX data"); + + data = cache_req_data_id_attrs(cmd_ctx, type, id, attrs); + if (data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to set cache request data!\n"); + ret = ENOMEM; + goto done; + } + + ret = eval_flags(cmd_ctx, data); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "eval_flags failed.\n"); + goto done; + } + + subreq = sss_nss_get_object_send(cmd_ctx, cli_ctx->ev, cli_ctx, + data, memcache, NULL, id); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_nss_get_object_send() failed\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sss_nss_getby_done, cmd_ctx); + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(cmd_ctx); + return sss_nss_protocol_done(cli_ctx, ret); + } + + return EOK; +} + +static errno_t sss_nss_getby_svc(struct cli_ctx *cli_ctx, + enum cache_req_type type, + const char *protocol, + const char *name, + uint16_t port, + sss_nss_protocol_fill_packet_fn fill_fn) +{ + struct cache_req_data *data; + struct sss_nss_cmd_ctx *cmd_ctx; + struct tevent_req *subreq; + errno_t ret; + + cmd_ctx = sss_nss_cmd_ctx_create(cli_ctx, cli_ctx, type, fill_fn); + if (cmd_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + cmd_ctx->svc_protocol = protocol; + + data = cache_req_data_svc(cmd_ctx, type, name, protocol, port); + if (data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to set cache request data!\n"); + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Input name: %s, protocol: %s, port: %u\n", + (name == NULL ? "<none>" : name), + (protocol == NULL ? "<none>" : protocol), + port); + + subreq = sss_nss_get_object_send(cmd_ctx, cli_ctx->ev, cli_ctx, + data, SSS_MC_NONE, NULL, 0); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_nss_get_object_send() failed\n"); + return ENOMEM; + } + + tevent_req_set_callback(subreq, sss_nss_getby_done, cmd_ctx); + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(cmd_ctx); + return sss_nss_protocol_done(cli_ctx, ret); + } + + return EOK; +} + +static errno_t sss_nss_getlistby_cert(struct cli_ctx *cli_ctx, + enum cache_req_type type) +{ + struct sss_nss_cmd_ctx *cmd_ctx; + struct tevent_req *subreq; + const char *cert; + errno_t ret; + + cmd_ctx = sss_nss_cmd_ctx_create(cli_ctx, cli_ctx, type, NULL); + if (cmd_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + cmd_ctx->sid_id_type = SSS_ID_TYPE_UID; + + ret = sss_nss_protocol_parse_cert(cli_ctx, &cert); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid request message!\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Input cert: %s\n", get_last_x_chars(cert, 10)); + + subreq = cache_req_user_by_cert_send(cmd_ctx, cli_ctx->ev, cli_ctx->rctx, + cli_ctx->rctx->ncache, 0, + CACHE_REQ_ANY_DOM, NULL, + cert); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "cache_req_user_by_cert_send failed.\n"); + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, sss_nss_getlistby_done, cmd_ctx); + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(cmd_ctx); + return sss_nss_protocol_done(cli_ctx, ret); + } + + return EOK; +} + +static void sss_nss_getlistby_done(struct tevent_req *subreq) +{ + struct cache_req_result **results; + struct sss_nss_cmd_ctx *cmd_ctx; + errno_t ret; + struct cli_protocol *pctx; + + cmd_ctx = tevent_req_callback_data(subreq, struct sss_nss_cmd_ctx); + + ret = cache_req_recv(cmd_ctx, subreq, &results); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "cache_req_user_by_cert request failed.\n"); + goto done; + } + + pctx = talloc_get_type(cmd_ctx->cli_ctx->protocol_ctx, struct cli_protocol); + + ret = sss_packet_new(pctx->creq, 0, sss_packet_get_cmd(pctx->creq->in), + &pctx->creq->out); + if (ret != EOK) { + goto done; + } + + ret = sss_nss_protocol_fill_name_list_all_domains(cmd_ctx->nss_ctx, cmd_ctx, + pctx->creq->out, results); + if (ret != EOK) { + goto done; + } + + sss_packet_set_error(pctx->creq->out, EOK); + +done: + sss_nss_protocol_done(cmd_ctx->cli_ctx, ret); + talloc_free(cmd_ctx); +} + +static errno_t sss_nss_getby_cert(struct cli_ctx *cli_ctx, + enum cache_req_type type, + sss_nss_protocol_fill_packet_fn fill_fn) +{ + struct cache_req_data *data; + struct sss_nss_cmd_ctx *cmd_ctx; + struct tevent_req *subreq; + const char *cert; + errno_t ret; + + cmd_ctx = sss_nss_cmd_ctx_create(cli_ctx, cli_ctx, type, fill_fn); + if (cmd_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + cmd_ctx->sid_id_type = SSS_ID_TYPE_UID; + + ret = sss_nss_protocol_parse_cert(cli_ctx, &cert); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid request message!\n"); + goto done; + } + + data = cache_req_data_cert(cmd_ctx, type, cert); + if (data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to set cache request data!\n"); + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Input cert: %s\n", get_last_x_chars(cert, 10)); + + subreq = sss_nss_get_object_send(cmd_ctx, cli_ctx->ev, cli_ctx, + data, SSS_MC_NONE, NULL, 0); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_nss_get_object_send() failed\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sss_nss_getby_done, cmd_ctx); + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(cmd_ctx); + return sss_nss_protocol_done(cli_ctx, ret); + } + + return EOK; +} + +static errno_t sss_nss_getby_sid(struct cli_ctx *cli_ctx, + enum cache_req_type type, + sss_nss_protocol_fill_packet_fn fill_fn) +{ + struct cache_req_data *data; + struct sss_nss_cmd_ctx *cmd_ctx; + struct tevent_req *subreq; + const char *sid; + errno_t ret; + + cmd_ctx = sss_nss_cmd_ctx_create(cli_ctx, cli_ctx, type, fill_fn); + if (cmd_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + /* It will be detected when constructing output packet. */ + cmd_ctx->sid_id_type = SSS_ID_TYPE_NOT_SPECIFIED; + + ret = sss_nss_protocol_parse_sid(cli_ctx, &sid); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid request message!\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Input SID: %s (looking up '%s')\n", sid, + (fill_fn == sss_nss_protocol_fill_name) ? "name" + : ((fill_fn == sss_nss_protocol_fill_id) ? "id" : "")); + + data = cache_req_data_sid(cmd_ctx, type, sid, NULL); + if (data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to set cache request data!\n"); + ret = ENOMEM; + goto done; + } + + subreq = sss_nss_get_object_send(cmd_ctx, cli_ctx->ev, cli_ctx, + data, SSS_MC_NONE, NULL, 0); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_nss_get_object_send() failed\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sss_nss_getby_done, cmd_ctx); + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(cmd_ctx); + return sss_nss_protocol_done(cli_ctx, ret); + } + + return EOK; +} + +static errno_t sss_nss_getby_addr(struct cli_ctx *cli_ctx, + enum cache_req_type type, + enum sss_mc_type memcache, + sss_nss_protocol_fill_packet_fn fill_fn) +{ + struct cache_req_data *data; + struct sss_nss_cmd_ctx *cmd_ctx; + struct tevent_req *subreq; + uint8_t *addr; + uint32_t addrlen; + uint32_t af; + errno_t ret; + + cmd_ctx = sss_nss_cmd_ctx_create(cli_ctx, cli_ctx, type, fill_fn); + if (cmd_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + cmd_ctx->flags = 0; + ret = sss_nss_protocol_parse_addr(cli_ctx, &af, &addrlen, &addr); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to parse address: %s\n", + strerror(ret)); + goto done; + } + + data = cache_req_data_addr(cmd_ctx, type, af, addrlen, addr); + if (data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set cache request data!\n"); + ret = ENOMEM; + goto done; + } + + subreq = sss_nss_get_object_send(cmd_ctx, cli_ctx->ev, cli_ctx, + data, memcache, NULL, 0); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_nss_get_object_send() failed\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sss_nss_getby_done, cmd_ctx); + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(cmd_ctx); + return sss_nss_protocol_done(cli_ctx, ret); + } + + return EOK; +} + +static errno_t invalidate_cache(struct sss_nss_cmd_ctx *cmd_ctx, + struct cache_req_result *result) +{ + int ret; + enum sss_mc_type memcache_type; + const char *name; + bool is_user; + struct sysdb_attrs *attrs = NULL; + + switch (cmd_ctx->type) { + case CACHE_REQ_INITGROUPS: + case CACHE_REQ_INITGROUPS_BY_UPN: + memcache_type = SSS_MC_INITGROUPS; + is_user = true; + break; + case CACHE_REQ_USER_BY_NAME: + case CACHE_REQ_USER_BY_ID: + memcache_type = SSS_MC_PASSWD; + is_user = true; + break; + case CACHE_REQ_GROUP_BY_NAME: + case CACHE_REQ_GROUP_BY_ID: + memcache_type = SSS_MC_GROUP; + is_user = false; + break; + default: + /* nothing to do - + * other requests don't support SSS_NSS_EX_FLAG_INVALIDATE_CACHE + */ + return EOK; + } + + /* Find output name to invalidate memory cache entry */ + name = sss_get_name_from_msg(result->domain, result->msgs[0]); + if (name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Found object has no name.\n"); + return EINVAL; + } + + memcache_delete_entry(cmd_ctx->nss_ctx, cmd_ctx->nss_ctx->rctx, NULL, + name, 0, memcache_type); + if (memcache_type == SSS_MC_INITGROUPS) { + /* Invalidate the passwd data as well */ + memcache_delete_entry(cmd_ctx->nss_ctx, cmd_ctx->nss_ctx->rctx, + result->domain, name, 0, SSS_MC_PASSWD); + } + + /* Use sysdb name to invalidate disk cache entry */ + name = ldb_msg_find_attr_as_string(result->msgs[0], SYSDB_NAME, NULL); + if (name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Found object has no name.\n"); + return EINVAL; + } + + if (memcache_type == SSS_MC_INITGROUPS) { + attrs = sysdb_new_attrs(cmd_ctx); + if (attrs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_new_attrs failed.\n"); + return ENOMEM; + } + + ret = sysdb_attrs_add_time_t(attrs, SYSDB_INITGR_EXPIRE, 1); + if (ret != EOK) { + talloc_free(attrs); + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_add_time_t failed.\n"); + return ret; + } + + ret = sysdb_set_user_attr(result->domain, name, attrs, SYSDB_MOD_REP); + talloc_free(attrs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_set_user_attr failed.\n"); + return ret; + } + } + + ret = sysdb_invalidate_cache_entry(result->domain, name, is_user); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_invalidate_cache_entry failed.\n"); + return ret; + } + + return EOK; +} + +static void sss_nss_getby_done(struct tevent_req *subreq) +{ + struct cache_req_result *result; + struct sss_nss_cmd_ctx *cmd_ctx; + errno_t ret; + + cmd_ctx = tevent_req_callback_data(subreq, struct sss_nss_cmd_ctx); + + ret = sss_nss_get_object_recv(cmd_ctx, subreq, &result, &cmd_ctx->rawname); + talloc_zfree(subreq); + if (ret != EOK) { + sss_nss_protocol_done(cmd_ctx->cli_ctx, ret); + goto done; + } + + if ((cmd_ctx->flags & SSS_NSS_EX_FLAG_INVALIDATE_CACHE) != 0) { + ret = invalidate_cache(cmd_ctx, result); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to invalidate cache for [%s].\n", + cmd_ctx->rawname); + sss_nss_protocol_done(cmd_ctx->cli_ctx, ret); + goto done; + } + } + + sss_nss_protocol_reply(cmd_ctx->cli_ctx, cmd_ctx->nss_ctx, cmd_ctx, + result, cmd_ctx->fill_fn); + +done: + talloc_free(cmd_ctx); +} + +static void sss_nss_setent_done(struct tevent_req *subreq); + +static errno_t sss_nss_setent(struct cli_ctx *cli_ctx, + enum cache_req_type type, + struct sss_nss_enum_ctx *enum_ctx) +{ + struct tevent_req *subreq; + + subreq = sss_nss_setent_send(cli_ctx, cli_ctx->ev, cli_ctx, type, enum_ctx); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_nss_setent_send() failed\n"); + return ENOMEM; + } + + tevent_req_set_callback(subreq, sss_nss_setent_done, cli_ctx); + + return EOK; +} + +static void sss_nss_setent_done(struct tevent_req *subreq) +{ + struct cli_ctx *cli_ctx; + errno_t ret; + + cli_ctx = tevent_req_callback_data(subreq, struct cli_ctx); + + ret = sss_nss_setent_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK && ret != ENOENT) { + sss_nss_protocol_done(cli_ctx, ret); + return; + } + + /* Both EOK and ENOENT means that setent was successful. */ + sss_nss_protocol_done(cli_ctx, EOK); +} + +static void sss_nss_getent_done(struct tevent_req *subreq); + +static errno_t sss_nss_getent(struct cli_ctx *cli_ctx, + enum cache_req_type type, + struct sss_nss_enum_index *idx, + sss_nss_protocol_fill_packet_fn fill_fn, + struct sss_nss_enum_ctx *enum_ctx) +{ + struct sss_nss_cmd_ctx *cmd_ctx; + struct tevent_req *subreq; + errno_t ret; + + cmd_ctx = sss_nss_cmd_ctx_create(cli_ctx, cli_ctx, type, fill_fn); + if (cmd_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sss_nss_protocol_parse_limit(cli_ctx, &cmd_ctx->enum_limit); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid request message!\n"); + goto done; + } + + cmd_ctx->enumeration = true; + cmd_ctx->enum_ctx = enum_ctx; + cmd_ctx->enum_index = idx; + + subreq = sss_nss_setent_send(cli_ctx, cli_ctx->ev, cli_ctx, type, enum_ctx); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_nss_setent_send() failed\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sss_nss_getent_done, cmd_ctx); + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(cmd_ctx); + return sss_nss_protocol_done(cli_ctx, ret); + } + + return ret; +} + +static struct cache_req_result * +sss_nss_getent_get_result(struct sss_nss_enum_ctx *enum_ctx, + struct sss_nss_enum_index *idx) +{ + struct cache_req_result *result; + + if (enum_ctx->result == NULL) { + /* Nothing was found. */ + return NULL; + } + + result = enum_ctx->result[idx->domain]; + + if (result != NULL && idx->result >= result->count) { + /* Switch to next domain. */ + idx->result = 0; + idx->domain++; + + result = enum_ctx->result[idx->domain]; + } + + return result; +} + +static void sss_nss_getent_done(struct tevent_req *subreq) +{ + struct cache_req_result *limited; + struct cache_req_result *result; + struct sss_nss_cmd_ctx *cmd_ctx; + errno_t ret; + + cmd_ctx = tevent_req_callback_data(subreq, struct sss_nss_cmd_ctx); + + ret = sss_nss_setent_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + result = sss_nss_getent_get_result(cmd_ctx->enum_ctx, cmd_ctx->enum_index); + if (result == NULL) { + /* No more records to return. */ + ret = ENOENT; + goto done; + } + + /* Create copy of the result with limited number of records. */ + limited = cache_req_copy_limited_result(cmd_ctx, result, + cmd_ctx->enum_index->result, + cmd_ctx->enum_limit); + if (limited == NULL) { + ret = ERR_INTERNAL; + goto done; + } + + cmd_ctx->enum_index->result += result->count; + + /* Reply with limited result. */ + sss_nss_protocol_reply(cmd_ctx->cli_ctx, cmd_ctx->nss_ctx, cmd_ctx, + result, cmd_ctx->fill_fn); + + ret = EOK; + +done: + if (ret != EOK) { + sss_nss_protocol_done(cmd_ctx->cli_ctx, ret); + } + + talloc_free(cmd_ctx); +} + +static void sss_nss_setnetgrent_done(struct tevent_req *subreq); + +/* This function's name started to collide with external nss symbol, + * so it has additional sss_* prefix unlike other functions here. */ +static errno_t sss_nss_setnetgrent(struct cli_ctx *cli_ctx, + enum cache_req_type type, + sss_nss_protocol_fill_packet_fn fill_fn) +{ + struct sss_nss_ctx *nss_ctx; + struct sss_nss_state_ctx *state_ctx; + struct sss_nss_cmd_ctx *cmd_ctx; + struct tevent_req *subreq; + const char *netgroup; + errno_t ret; + + cmd_ctx = sss_nss_cmd_ctx_create(cli_ctx, cli_ctx, type, fill_fn); + if (cmd_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + nss_ctx = cmd_ctx->nss_ctx; + state_ctx = cmd_ctx->state_ctx; + + ret = sss_nss_protocol_parse_name(cli_ctx, &netgroup); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid request message!\n"); + goto done; + } + + state_ctx->netgrent.domain = 0; + state_ctx->netgrent.result = 0; + + talloc_zfree(state_ctx->netgroup); + state_ctx->netgroup = talloc_strdup(state_ctx, netgroup); + if (state_ctx->netgroup == NULL) { + ret = ENOMEM; + goto done; + } + + /* enum_limit is not used for setnetgrent, all results will be returned at + * once to allow innetgr() to be implemented in the thread-safe way. */ + cmd_ctx->enum_limit = 0; + cmd_ctx->enumeration = true; + cmd_ctx->enum_ctx = NULL; /* We will determine it later. */ + cmd_ctx->enum_index = &cmd_ctx->state_ctx->netgrent; + + subreq = sss_nss_setnetgrent_send(cli_ctx, cli_ctx->ev, cli_ctx, type, + nss_ctx->netgrent, state_ctx->netgroup); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "sss_nss_setnetgrent_send() failed\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sss_nss_setnetgrent_done, cmd_ctx); + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(cmd_ctx); + return sss_nss_protocol_done(cli_ctx, ret); + } + + return EOK; +} + +static void sss_nss_setnetgrent_done(struct tevent_req *subreq) +{ + struct sss_nss_enum_ctx *enum_ctx; + struct sss_nss_cmd_ctx *cmd_ctx; + errno_t ret; + + cmd_ctx = tevent_req_callback_data(subreq, struct sss_nss_cmd_ctx); + + ret = sss_nss_setent_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + enum_ctx = sss_ptr_hash_lookup(cmd_ctx->nss_ctx->netgrent, + cmd_ctx->state_ctx->netgroup, + struct sss_nss_enum_ctx); + if (enum_ctx == NULL) { + ret = ENOENT; + goto done; + } + + cmd_ctx->enum_ctx = enum_ctx; + + /* Reply with result. */ + sss_nss_protocol_reply(cmd_ctx->cli_ctx, cmd_ctx->nss_ctx, cmd_ctx, + NULL, cmd_ctx->fill_fn); + + ret = EOK; + +done: + if (ret != EOK) { + sss_nss_protocol_done(cmd_ctx->cli_ctx, ret); + } + + cmd_ctx->state_ctx->netgrent.domain = 0; + cmd_ctx->state_ctx->netgrent.result = 0; + + talloc_free(cmd_ctx); +} + +static errno_t sss_nss_endent(struct cli_ctx *cli_ctx, + struct sss_nss_enum_index *idx) +{ + DEBUG(SSSDBG_CONF_SETTINGS, "Resetting enumeration state\n"); + + idx->domain = 0; + idx->result = 0; + + sss_nss_protocol_done(cli_ctx, EOK); + + return EOK; +} + +static errno_t sss_nss_cmd_getpwnam(struct cli_ctx *cli_ctx) +{ + return sss_nss_getby_name(cli_ctx, false, CACHE_REQ_USER_BY_NAME, NULL, + SSS_MC_PASSWD, sss_nss_protocol_fill_pwent); +} + +static errno_t sss_nss_cmd_getpwuid(struct cli_ctx *cli_ctx) +{ + return sss_nss_getby_id(cli_ctx, false, CACHE_REQ_USER_BY_ID, NULL, + SSS_MC_PASSWD, sss_nss_protocol_fill_pwent); +} + +static errno_t sss_nss_cmd_getpwnam_ex(struct cli_ctx *cli_ctx) +{ + return sss_nss_getby_name(cli_ctx, true, CACHE_REQ_USER_BY_NAME, NULL, + SSS_MC_PASSWD, sss_nss_protocol_fill_pwent); +} + +static errno_t sss_nss_cmd_getpwuid_ex(struct cli_ctx *cli_ctx) +{ + return sss_nss_getby_id(cli_ctx, true, CACHE_REQ_USER_BY_ID, NULL, + SSS_MC_PASSWD, sss_nss_protocol_fill_pwent); +} + +static errno_t sss_nss_cmd_setpwent(struct cli_ctx *cli_ctx) +{ + struct sss_nss_ctx *nss_ctx; + struct sss_nss_state_ctx *state_ctx; + + state_ctx = talloc_get_type(cli_ctx->state_ctx, struct sss_nss_state_ctx); + state_ctx->pwent.domain = 0; + state_ctx->pwent.result = 0; + + nss_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct sss_nss_ctx); + + return sss_nss_setent(cli_ctx, CACHE_REQ_ENUM_USERS, nss_ctx->pwent); +} + +static errno_t sss_nss_cmd_getpwent(struct cli_ctx *cli_ctx) +{ + struct sss_nss_ctx *nss_ctx; + struct sss_nss_state_ctx *state_ctx; + + nss_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct sss_nss_ctx); + state_ctx = talloc_get_type(cli_ctx->state_ctx, struct sss_nss_state_ctx); + + return sss_nss_getent(cli_ctx, CACHE_REQ_ENUM_USERS, + &state_ctx->pwent, sss_nss_protocol_fill_pwent, + nss_ctx->pwent); +} + +static errno_t sss_nss_cmd_endpwent(struct cli_ctx *cli_ctx) +{ + struct sss_nss_state_ctx *state_ctx; + + state_ctx = talloc_get_type(cli_ctx->state_ctx, struct sss_nss_state_ctx); + + return sss_nss_endent(cli_ctx, &state_ctx->pwent); +} + +static errno_t sss_nss_cmd_getgrnam(struct cli_ctx *cli_ctx) +{ + return sss_nss_getby_name(cli_ctx, false, CACHE_REQ_GROUP_BY_NAME, NULL, + SSS_MC_GROUP, sss_nss_protocol_fill_grent); +} + +static errno_t sss_nss_cmd_getgrgid(struct cli_ctx *cli_ctx) +{ + return sss_nss_getby_id(cli_ctx, false, CACHE_REQ_GROUP_BY_ID, NULL, + SSS_MC_GROUP, sss_nss_protocol_fill_grent); +} + +static errno_t sss_nss_cmd_getgrnam_ex(struct cli_ctx *cli_ctx) +{ + return sss_nss_getby_name(cli_ctx, true, CACHE_REQ_GROUP_BY_NAME, NULL, + SSS_MC_GROUP, sss_nss_protocol_fill_grent); +} + +static errno_t sss_nss_cmd_getgrgid_ex(struct cli_ctx *cli_ctx) +{ + return sss_nss_getby_id(cli_ctx, true, CACHE_REQ_GROUP_BY_ID, NULL, + SSS_MC_GROUP, sss_nss_protocol_fill_grent); +} + + +static errno_t sss_nss_cmd_setgrent(struct cli_ctx *cli_ctx) +{ + struct sss_nss_ctx *nss_ctx; + struct sss_nss_state_ctx *state_ctx; + + state_ctx = talloc_get_type(cli_ctx->state_ctx, struct sss_nss_state_ctx); + state_ctx->grent.domain = 0; + state_ctx->grent.result = 0; + + nss_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct sss_nss_ctx); + + return sss_nss_setent(cli_ctx, CACHE_REQ_ENUM_GROUPS, nss_ctx->grent); +} + +static errno_t sss_nss_cmd_getgrent(struct cli_ctx *cli_ctx) +{ + struct sss_nss_ctx *nss_ctx; + struct sss_nss_state_ctx *state_ctx; + + nss_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct sss_nss_ctx); + state_ctx = talloc_get_type(cli_ctx->state_ctx, struct sss_nss_state_ctx); + + return sss_nss_getent(cli_ctx, CACHE_REQ_ENUM_GROUPS, + &state_ctx->grent, sss_nss_protocol_fill_grent, + nss_ctx->grent); +} + +static errno_t sss_nss_cmd_endgrent(struct cli_ctx *cli_ctx) +{ + struct sss_nss_state_ctx *state_ctx; + + state_ctx = talloc_get_type(cli_ctx->state_ctx, struct sss_nss_state_ctx); + + return sss_nss_endent(cli_ctx, &state_ctx->grent); +} + +static errno_t sss_nss_cmd_initgroups(struct cli_ctx *cli_ctx) +{ + return sss_nss_getby_name(cli_ctx, false, CACHE_REQ_INITGROUPS, NULL, + SSS_MC_INITGROUPS, sss_nss_protocol_fill_initgr); +} + +static errno_t sss_nss_cmd_initgroups_ex(struct cli_ctx *cli_ctx) +{ + return sss_nss_getby_name(cli_ctx, true, CACHE_REQ_INITGROUPS, NULL, + SSS_MC_INITGROUPS, sss_nss_protocol_fill_initgr); +} + +static errno_t sss_nss_cmd_subid_ranges(struct cli_ctx *cli_ctx) +{ +#ifdef BUILD_SUBID + const char *attrs[] = + { + SYSDB_SUBID_UID_COUND, + SYSDB_SUBID_GID_COUNT, + SYSDB_SUBID_UID_NUMBER, + SYSDB_SUBID_GID_NUMBER, + NULL + }; + + return sss_nss_getby_name(cli_ctx, false, CACHE_REQ_SUBID_RANGES_BY_NAME, attrs, + SSS_MC_NONE, sss_nss_protocol_fill_subid_ranges); +#else + return ENOTSUP; +#endif +} + +static errno_t sss_nss_cmd_setnetgrent(struct cli_ctx *cli_ctx) +{ + struct sss_nss_state_ctx *state_ctx; + + state_ctx = talloc_get_type(cli_ctx->state_ctx, struct sss_nss_state_ctx); + state_ctx->netgrent.domain = 0; + state_ctx->netgrent.result = 0; + + return sss_nss_setnetgrent(cli_ctx, CACHE_REQ_NETGROUP_BY_NAME, + sss_nss_protocol_fill_netgrent); +} + +static errno_t sss_nss_cmd_getservbyname(struct cli_ctx *cli_ctx) +{ + const char *name; + const char *protocol; + errno_t ret; + + ret = sss_nss_protocol_parse_svc_name(cli_ctx, &name, &protocol); + if (ret != EOK) { + return ret; + } + + return sss_nss_getby_svc(cli_ctx, CACHE_REQ_SVC_BY_NAME, protocol, name, 0, + sss_nss_protocol_fill_svcent); +} + +static errno_t sss_nss_cmd_getservbyport(struct cli_ctx *cli_ctx) +{ + const char *protocol; + uint16_t port; + errno_t ret; + + ret = sss_nss_protocol_parse_svc_port(cli_ctx, &port, &protocol); + if (ret != EOK) { + return ret; + } + + return sss_nss_getby_svc(cli_ctx, CACHE_REQ_SVC_BY_PORT, protocol, NULL, port, + sss_nss_protocol_fill_svcent); +} + +static errno_t sss_nss_cmd_setservent(struct cli_ctx *cli_ctx) +{ + struct sss_nss_ctx *nss_ctx; + struct sss_nss_state_ctx *state_ctx; + + state_ctx = talloc_get_type(cli_ctx->state_ctx, struct sss_nss_state_ctx); + state_ctx->svcent.domain = 0; + state_ctx->svcent.result = 0; + + nss_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct sss_nss_ctx); + + return sss_nss_setent(cli_ctx, CACHE_REQ_ENUM_SVC, nss_ctx->svcent); +} + +static errno_t sss_nss_cmd_getservent(struct cli_ctx *cli_ctx) +{ + struct sss_nss_ctx *nss_ctx; + struct sss_nss_state_ctx *state_ctx; + + nss_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct sss_nss_ctx); + state_ctx = talloc_get_type(cli_ctx->state_ctx, struct sss_nss_state_ctx); + + return sss_nss_getent(cli_ctx, CACHE_REQ_ENUM_SVC, + &state_ctx->svcent, sss_nss_protocol_fill_svcent, + nss_ctx->svcent); +} + +static errno_t sss_nss_cmd_endservent(struct cli_ctx *cli_ctx) +{ + struct sss_nss_state_ctx *state_ctx; + + state_ctx = talloc_get_type(cli_ctx->state_ctx, struct sss_nss_state_ctx); + + return sss_nss_endent(cli_ctx, &state_ctx->svcent); +} + +static errno_t sss_nss_cmd_getsidbyname(struct cli_ctx *cli_ctx) +{ + /* The attributes besides SYSDB_SID_STR are needed to handle some corner + * cases with respect to user-private-groups */ + const char *attrs[] = { SYSDB_SID_STR, SYSDB_UIDNUM, SYSDB_GIDNUM, + SYSDB_OBJECTCATEGORY, NULL }; + + return sss_nss_getby_name(cli_ctx, false, CACHE_REQ_OBJECT_BY_NAME, attrs, + SSS_MC_NONE, sss_nss_protocol_fill_sid); +} + +static errno_t sss_nss_cmd_getsidbyusername(struct cli_ctx *cli_ctx) +{ + /* The attributes besides SYSDB_SID_STR are needed to handle some corner + * cases with respect to user-private-groups */ + const char *attrs[] = { SYSDB_SID_STR, SYSDB_UIDNUM, SYSDB_GIDNUM, + SYSDB_OBJECTCATEGORY, NULL }; + + return sss_nss_getby_name(cli_ctx, false, CACHE_REQ_USER_BY_NAME, attrs, + SSS_MC_NONE, sss_nss_protocol_fill_sid); +} + +static errno_t sss_nss_cmd_getsidbygroupname(struct cli_ctx *cli_ctx) +{ + /* The attributes besides SYSDB_SID_STR are needed to handle some corner + * cases with respect to user-private-groups */ + const char *attrs[] = { SYSDB_SID_STR, SYSDB_UIDNUM, SYSDB_GIDNUM, + SYSDB_OBJECTCATEGORY, NULL }; + + return sss_nss_getby_name(cli_ctx, false, CACHE_REQ_GROUP_BY_NAME, attrs, + SSS_MC_NONE, sss_nss_protocol_fill_sid); +} + +static errno_t sss_nss_cmd_getsidbyid(struct cli_ctx *cli_ctx) +{ + const char *attrs[] = { SYSDB_SID_STR, SYSDB_UIDNUM, SYSDB_GIDNUM, + SYSDB_OBJECTCATEGORY, NULL }; + + return sss_nss_getby_id(cli_ctx, false, CACHE_REQ_OBJECT_BY_ID, attrs, + SSS_MC_SID, sss_nss_protocol_fill_sid); +} + +static errno_t sss_nss_cmd_getsidbyuid(struct cli_ctx *cli_ctx) +{ + const char *attrs[] = { SYSDB_SID_STR, SYSDB_UIDNUM, SYSDB_GIDNUM, + SYSDB_OBJECTCATEGORY, NULL }; + + return sss_nss_getby_id(cli_ctx, false, CACHE_REQ_USER_BY_ID, attrs, + SSS_MC_SID, sss_nss_protocol_fill_sid); +} + +static errno_t sss_nss_cmd_getsidbygid(struct cli_ctx *cli_ctx) +{ + const char *attrs[] = { SYSDB_SID_STR, SYSDB_UIDNUM, SYSDB_GIDNUM, + SYSDB_OBJECTCATEGORY, NULL }; + + return sss_nss_getby_id(cli_ctx, false, CACHE_REQ_GROUP_BY_ID, attrs, + SSS_MC_SID, sss_nss_protocol_fill_sid); +} + +static errno_t sss_nss_cmd_getnamebysid(struct cli_ctx *cli_ctx) +{ + return sss_nss_getby_sid(cli_ctx, CACHE_REQ_OBJECT_BY_SID, + sss_nss_protocol_fill_name); +} + +static errno_t sss_nss_cmd_getidbysid(struct cli_ctx *cli_ctx) +{ + return sss_nss_getby_sid(cli_ctx, CACHE_REQ_OBJECT_BY_SID, + sss_nss_protocol_fill_id); +} + +static errno_t sss_nss_cmd_getorigbyname_common(struct cli_ctx *cli_ctx, + enum cache_req_type type) +{ + errno_t ret; + struct sss_nss_ctx *nss_ctx; + const char **attrs; + static const char *cache_attrs[] = { SYSDB_NAME, + SYSDB_OBJECTCATEGORY, + SYSDB_SID_STR, + SYSDB_DEFAULT_ATTRS, + NULL }; + + nss_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct sss_nss_ctx); + + ret = add_strings_lists_ex(cli_ctx, cache_attrs, nss_ctx->full_attribute_list, + false, true, &attrs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Unable to concatenate attributes [%d]: %s\n", + ret, sss_strerror(ret)); + return ENOMEM; + } + + return sss_nss_getby_name(cli_ctx, false, type, attrs, + SSS_MC_NONE, sss_nss_protocol_fill_orig); +} + +static errno_t sss_nss_cmd_getorigbyname(struct cli_ctx *cli_ctx) +{ + return sss_nss_cmd_getorigbyname_common(cli_ctx, CACHE_REQ_OBJECT_BY_NAME); +} + +static errno_t sss_nss_cmd_getorigbyusername(struct cli_ctx *cli_ctx) +{ + return sss_nss_cmd_getorigbyname_common(cli_ctx, CACHE_REQ_USER_BY_NAME); +} + +static errno_t sss_nss_cmd_getorigbygroupname(struct cli_ctx *cli_ctx) +{ + return sss_nss_cmd_getorigbyname_common(cli_ctx, CACHE_REQ_GROUP_BY_NAME); +} + + +static errno_t sss_nss_cmd_getnamebycert(struct cli_ctx *cli_ctx) +{ + return sss_nss_getby_cert(cli_ctx, CACHE_REQ_USER_BY_CERT, + sss_nss_protocol_fill_single_name); +} + +static errno_t sss_nss_cmd_getlistbycert(struct cli_ctx *cli_ctx) +{ + return sss_nss_getlistby_cert(cli_ctx, CACHE_REQ_USER_BY_CERT); +} + +static errno_t sss_nss_cmd_gethostbyname(struct cli_ctx *cli_ctx) +{ + return sss_nss_getby_name(cli_ctx, false, CACHE_REQ_IP_HOST_BY_NAME, NULL, + SSS_MC_NONE, sss_nss_protocol_fill_hostent); +} + +static errno_t sss_nss_cmd_gethostbyaddr(struct cli_ctx *cli_ctx) +{ + return sss_nss_getby_addr(cli_ctx, CACHE_REQ_IP_HOST_BY_ADDR, + SSS_MC_NONE, sss_nss_protocol_fill_hostent); +} + +static errno_t sss_nss_cmd_sethostent(struct cli_ctx *cli_ctx) +{ + struct sss_nss_ctx *nss_ctx; + struct sss_nss_state_ctx *state_ctx; + + state_ctx = talloc_get_type(cli_ctx->state_ctx, struct sss_nss_state_ctx); + state_ctx->hostent.domain = 0; + state_ctx->hostent.result = 0; + + nss_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct sss_nss_ctx); + + return sss_nss_setent(cli_ctx, CACHE_REQ_ENUM_HOST, nss_ctx->hostent); +} + +static errno_t sss_nss_cmd_gethostent(struct cli_ctx *cli_ctx) +{ + struct sss_nss_ctx *nss_ctx; + struct sss_nss_state_ctx *state_ctx; + + nss_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct sss_nss_ctx); + state_ctx = talloc_get_type(cli_ctx->state_ctx, struct sss_nss_state_ctx); + + return sss_nss_getent(cli_ctx, CACHE_REQ_ENUM_HOST, + &state_ctx->hostent, sss_nss_protocol_fill_hostent, + nss_ctx->hostent); +} + +static errno_t sss_nss_cmd_endhostent(struct cli_ctx *cli_ctx) +{ + struct sss_nss_state_ctx *state_ctx; + + state_ctx = talloc_get_type(cli_ctx->state_ctx, struct sss_nss_state_ctx); + + return sss_nss_endent(cli_ctx, &state_ctx->hostent); +} + +static errno_t sss_nss_cmd_getnetbyname(struct cli_ctx *cli_ctx) +{ + return sss_nss_getby_name(cli_ctx, false, CACHE_REQ_IP_NETWORK_BY_NAME, NULL, + SSS_MC_NONE, sss_nss_protocol_fill_netent); +} + +static errno_t sss_nss_cmd_getnetbyaddr(struct cli_ctx *cli_ctx) +{ + return sss_nss_getby_addr(cli_ctx, CACHE_REQ_IP_NETWORK_BY_ADDR, + SSS_MC_NONE, sss_nss_protocol_fill_netent); +} + + +static errno_t sss_nss_cmd_setnetent(struct cli_ctx *cli_ctx) +{ + struct sss_nss_ctx *nss_ctx; + struct sss_nss_state_ctx *state_ctx; + + state_ctx = talloc_get_type(cli_ctx->state_ctx, struct sss_nss_state_ctx); + state_ctx->netent.domain = 0; + state_ctx->netent.result = 0; + + nss_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct sss_nss_ctx); + + return sss_nss_setent(cli_ctx, CACHE_REQ_ENUM_IP_NETWORK, nss_ctx->netent); +} + +static errno_t sss_nss_cmd_getnetent(struct cli_ctx *cli_ctx) +{ + struct sss_nss_ctx *nss_ctx; + struct sss_nss_state_ctx *state_ctx; + + nss_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct sss_nss_ctx); + state_ctx = talloc_get_type(cli_ctx->state_ctx, struct sss_nss_state_ctx); + + return sss_nss_getent(cli_ctx, CACHE_REQ_ENUM_IP_NETWORK, + &state_ctx->netent, sss_nss_protocol_fill_netent, + nss_ctx->netent); +} + +static errno_t sss_nss_cmd_endnetent(struct cli_ctx *cli_ctx) +{ + struct sss_nss_state_ctx *state_ctx; + + state_ctx = talloc_get_type(cli_ctx->state_ctx, struct sss_nss_state_ctx); + + return sss_nss_endent(cli_ctx, &state_ctx->netent); +} + +struct sss_cmd_table *get_sss_nss_cmds(void) +{ + static struct sss_cmd_table nss_cmds[] = { + { SSS_GET_VERSION, sss_cmd_get_version }, + { SSS_NSS_GETPWNAM, sss_nss_cmd_getpwnam }, + { SSS_NSS_GETPWUID, sss_nss_cmd_getpwuid }, + { SSS_NSS_SETPWENT, sss_nss_cmd_setpwent }, + { SSS_NSS_GETPWENT, sss_nss_cmd_getpwent }, + { SSS_NSS_ENDPWENT, sss_nss_cmd_endpwent }, + { SSS_NSS_GETGRNAM, sss_nss_cmd_getgrnam }, + { SSS_NSS_GETGRGID, sss_nss_cmd_getgrgid }, + { SSS_NSS_SETGRENT, sss_nss_cmd_setgrent }, + { SSS_NSS_GETGRENT, sss_nss_cmd_getgrent }, + { SSS_NSS_ENDGRENT, sss_nss_cmd_endgrent }, + { SSS_NSS_INITGR, sss_nss_cmd_initgroups }, + { SSS_NSS_GET_SUBID_RANGES, sss_nss_cmd_subid_ranges }, + { SSS_NSS_SETNETGRENT, sss_nss_cmd_setnetgrent }, + /* { SSS_NSS_GETNETGRENT, "not needed" }, */ + /* { SSS_NSS_ENDNETGRENT, "not needed" }, */ + { SSS_NSS_GETSERVBYNAME, sss_nss_cmd_getservbyname }, + { SSS_NSS_GETSERVBYPORT, sss_nss_cmd_getservbyport }, + { SSS_NSS_SETSERVENT, sss_nss_cmd_setservent }, + { SSS_NSS_GETSERVENT, sss_nss_cmd_getservent }, + { SSS_NSS_ENDSERVENT, sss_nss_cmd_endservent }, + { SSS_NSS_GETSIDBYNAME, sss_nss_cmd_getsidbyname }, + { SSS_NSS_GETSIDBYUSERNAME, sss_nss_cmd_getsidbyusername }, + { SSS_NSS_GETSIDBYGROUPNAME, sss_nss_cmd_getsidbygroupname }, + { SSS_NSS_GETSIDBYID, sss_nss_cmd_getsidbyid }, + { SSS_NSS_GETSIDBYUID, sss_nss_cmd_getsidbyuid }, + { SSS_NSS_GETSIDBYGID, sss_nss_cmd_getsidbygid }, + { SSS_NSS_GETNAMEBYSID, sss_nss_cmd_getnamebysid }, + { SSS_NSS_GETIDBYSID, sss_nss_cmd_getidbysid }, + { SSS_NSS_GETORIGBYNAME, sss_nss_cmd_getorigbyname }, + { SSS_NSS_GETORIGBYUSERNAME, sss_nss_cmd_getorigbyusername }, + { SSS_NSS_GETORIGBYGROUPNAME, sss_nss_cmd_getorigbygroupname }, + { SSS_NSS_GETNAMEBYCERT, sss_nss_cmd_getnamebycert }, + { SSS_NSS_GETLISTBYCERT, sss_nss_cmd_getlistbycert }, + { SSS_NSS_GETPWNAM_EX, sss_nss_cmd_getpwnam_ex }, + { SSS_NSS_GETPWUID_EX, sss_nss_cmd_getpwuid_ex }, + { SSS_NSS_GETGRNAM_EX, sss_nss_cmd_getgrnam_ex }, + { SSS_NSS_GETGRGID_EX, sss_nss_cmd_getgrgid_ex }, + { SSS_NSS_INITGR_EX, sss_nss_cmd_initgroups_ex }, + { SSS_NSS_GETHOSTBYNAME, sss_nss_cmd_gethostbyname }, + { SSS_NSS_GETHOSTBYNAME2, sss_nss_cmd_gethostbyname }, + { SSS_NSS_GETHOSTBYADDR, sss_nss_cmd_gethostbyaddr }, + { SSS_NSS_SETHOSTENT, sss_nss_cmd_sethostent }, + { SSS_NSS_GETHOSTENT, sss_nss_cmd_gethostent }, + { SSS_NSS_ENDHOSTENT, sss_nss_cmd_endhostent }, + { SSS_NSS_GETNETBYNAME, sss_nss_cmd_getnetbyname }, + { SSS_NSS_GETNETBYADDR, sss_nss_cmd_getnetbyaddr }, + { SSS_NSS_SETNETENT, sss_nss_cmd_setnetent }, + { SSS_NSS_GETNETENT, sss_nss_cmd_getnetent }, + { SSS_NSS_ENDNETENT, sss_nss_cmd_endnetent }, + { SSS_CLI_NULL, NULL } + }; + + return nss_cmds; +} + +struct cli_protocol_version *register_cli_protocol_version(void) +{ + static struct cli_protocol_version nss_cli_protocol_version[] = { + { 1, "2008-09-05", "initial version, \\0 terminated strings" }, + { 0, NULL, NULL } + }; + + return nss_cli_protocol_version; +} + +int sss_nss_connection_setup(struct cli_ctx *cli_ctx) +{ + int ret; + + ret = sss_connection_setup(cli_ctx); + if (ret != EOK) return ret; + + cli_ctx->state_ctx = talloc_zero(cli_ctx, struct sss_nss_state_ctx); + if (cli_ctx->state_ctx == NULL) { + return ENOMEM; + } + + return EOK; +} diff --git a/src/responder/nss/nss_enum.c b/src/responder/nss/nss_enum.c new file mode 100644 index 0000000..2b4fb60 --- /dev/null +++ b/src/responder/nss/nss_enum.c @@ -0,0 +1,361 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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 <tevent.h> +#include <talloc.h> + +#include "util/util.h" +#include "util/sss_ptr_hash.h" +#include "responder/nss/nss_private.h" + +typedef errno_t (*sss_nss_setent_set_timeout_fn)(struct tevent_context *ev, + struct sss_nss_ctx *nss_ctx, + struct sss_nss_enum_ctx *enum_ctx); + +struct sss_nss_setent_internal_state { + struct tevent_context *ev; + struct sss_nss_ctx *nss_ctx; + struct sss_nss_enum_ctx *enum_ctx; + sss_nss_setent_set_timeout_fn timeout_handler; + enum cache_req_type type; +}; + +static void sss_nss_setent_internal_done(struct tevent_req *subreq); + +/* Cache request data is stealed on internal state. */ +static struct tevent_req * +sss_nss_setent_internal_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct cli_ctx *cli_ctx, + struct cache_req_data *data, + enum cache_req_type type, + struct sss_nss_enum_ctx *enum_ctx, + sss_nss_setent_set_timeout_fn timeout_handler) +{ + struct sss_nss_setent_internal_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct sss_nss_setent_internal_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + talloc_steal(state, data); + + state->ev = ev; + state->nss_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct sss_nss_ctx); + state->enum_ctx = enum_ctx; + state->type = type; + state->timeout_handler = timeout_handler; + + if (state->enum_ctx->is_ready) { + /* Object is already constructed, just return here. */ + talloc_free(data); + ret = EOK; + goto done; + } + + if (state->enum_ctx->ongoing != NULL) { + /* Object is being constructed. Register ourselves for + * notification when it is finished. */ + ret = setent_add_ref(state, &state->enum_ctx->notify_list, req); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to register setent reference [%d]: %s!\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = EAGAIN; + goto done; + } + + /* Create new object. */ + state->enum_ctx->is_ready = false; + subreq = cache_req_send(req, ev, cli_ctx->rctx, cli_ctx->rctx->ncache, + state->nss_ctx->cache_refresh_percent, + CACHE_REQ_POSIX_DOM, NULL, data); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to send cache request!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sss_nss_setent_internal_done, req); + state->enum_ctx->ongoing = subreq; + + ret = EAGAIN; + +done: + if (ret == EOK) { + tevent_req_done(req); + tevent_req_post(req, ev); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void sss_nss_setent_internal_done(struct tevent_req *subreq) +{ + struct cache_req_result **result; + struct sss_nss_setent_internal_state *state; + struct setent_req_list **notify_list; + struct tevent_req *req; + errno_t ret; + errno_t tret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sss_nss_setent_internal_state); + + /* This is the ongoing request and it is finished. Remove it. */ + state->enum_ctx->ongoing = NULL; + + ret = cache_req_recv(state, subreq, &result); + talloc_zfree(subreq); + + switch (ret) { + case EOK: + talloc_zfree(state->enum_ctx->result); + state->enum_ctx->result = talloc_steal(state->enum_ctx, result); + + if (state->type == CACHE_REQ_NETGROUP_BY_NAME) { + /* We need to expand the netgroup into triples and members. */ + ret = sysdb_netgr_to_entries(state->enum_ctx, + result[0]->ldb_result, + &state->enum_ctx->netgroup, + &state->enum_ctx->netgroup_count); + if (ret != EOK) { + goto done; + } + } + break; + case ENOENT: + /* Reset the result but build it again next time setent is called. */ + talloc_zfree(state->enum_ctx->result); + talloc_zfree(state->enum_ctx->netgroup); + goto done; + default: + /* In case of an error, we do not touch the enumeration context. */ + goto done; + } + + /* Expire the result object after its timeout is reached. */ + tret = state->timeout_handler(state->ev, state->nss_ctx, state->enum_ctx); + if (tret != EOK) { + ret = ENOMEM; + goto done; + } + + /* The object is ready now. */ + state->enum_ctx->is_ready = true; + + ret = EOK; + +done: + /* We want to finish the requests in correct order, this was the + * first request, notify_list contain the subsequent request. + * + * Because callback invoked from tevent_req_done will free state, + * we must remember notify_list explicitly to avoid segfault. + */ + notify_list = &state->enum_ctx->notify_list; + + if (ret == EOK) { + tevent_req_done(req); + setent_notify_done(notify_list); + } else { + tevent_req_error(req, ret); + setent_notify(notify_list, ret); + } +} + +static errno_t +sss_nss_setent_internal_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +static void +sss_nss_setent_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *pvt) +{ + struct sss_nss_enum_ctx *enum_ctx = pvt; + + DEBUG(SSSDBG_TRACE_FUNC, "Enumeration result object has expired.\n"); + + /* Reset enumeration context. */ + talloc_zfree(enum_ctx->result); + enum_ctx->is_ready = false; +} + +static errno_t +sss_nss_setent_set_timeout(struct tevent_context *ev, + struct sss_nss_ctx *nss_ctx, + struct sss_nss_enum_ctx *enum_ctx) +{ + struct tevent_timer *te; + struct timeval tv; + + tv = tevent_timeval_current_ofs(nss_ctx->enum_cache_timeout, 0); + te = tevent_add_timer(ev, nss_ctx, tv, sss_nss_setent_timeout, enum_ctx); + if (te == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not set up life timer for enumeration object.\n"); + return ENOMEM; + } + + return EOK; +} + +struct tevent_req * +sss_nss_setent_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct cli_ctx *cli_ctx, + enum cache_req_type type, + struct sss_nss_enum_ctx *enum_ctx) +{ + struct cache_req_data *data; + + data = cache_req_data_enum(mem_ctx, type); + if (data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to set cache request data!\n"); + return NULL; + } + + return sss_nss_setent_internal_send(mem_ctx, ev, cli_ctx, data, type, enum_ctx, + sss_nss_setent_set_timeout); +} + +errno_t sss_nss_setent_recv(struct tevent_req *req) +{ + return sss_nss_setent_internal_recv(req); +} + +static void +sss_nss_setnetgrent_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *pvt) +{ + struct sss_nss_enum_ctx *enum_ctx; + + DEBUG(SSSDBG_TRACE_FUNC, "Enumeration result object has expired.\n"); + + /* Free enumeration context. This will also remove it from the table. */ + enum_ctx = talloc_get_type(pvt, struct sss_nss_enum_ctx); + talloc_free(enum_ctx); +} + +static errno_t +sss_nss_setnetgrent_set_timeout(struct tevent_context *ev, + struct sss_nss_ctx *nss_ctx, + struct sss_nss_enum_ctx *enum_ctx) +{ + struct tevent_timer *te; + struct timeval tv; + uint32_t timeout; + + if (nss_ctx->cache_refresh_percent) { + timeout = enum_ctx->result[0]->domain->netgroup_timeout * + (nss_ctx->cache_refresh_percent / 100.0); + } else { + timeout = enum_ctx->result[0]->domain->netgroup_timeout; + } + + /* In order to not trash the cache between setnetgrent()/getnetgrent() + * calls with too low timeout values, we only allow 10 seconds as + * the minimal timeout + */ + if (timeout < 10) timeout = 10; + + tv = tevent_timeval_current_ofs(timeout, 0); + te = tevent_add_timer(ev, enum_ctx, tv, sss_nss_setnetgrent_timeout, enum_ctx); + if (te == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not set up life timer for enumeration object.\n"); + return ENOMEM; + } + + return EOK; +} + +static struct sss_nss_enum_ctx * +sss_nss_setnetgrent_set_enum_ctx(hash_table_t *table, + const char *netgroup) +{ + struct sss_nss_enum_ctx *enum_ctx; + errno_t ret; + + enum_ctx = sss_ptr_hash_lookup(table, netgroup, struct sss_nss_enum_ctx); + if (enum_ctx != NULL) { + return enum_ctx; + } + + enum_ctx = talloc_zero(table, struct sss_nss_enum_ctx); + if (enum_ctx == NULL) { + return NULL; + } + + ret = sss_ptr_hash_add(table, netgroup, enum_ctx, struct sss_nss_enum_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to add enumeration context into table [%d]: %s\n", + ret, sss_strerror(ret)); + talloc_free(enum_ctx); + return NULL; + } + + return enum_ctx; +} + +struct tevent_req * +sss_nss_setnetgrent_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct cli_ctx *cli_ctx, + enum cache_req_type type, + hash_table_t *table, + const char *netgroup) +{ + struct sss_nss_enum_ctx *enum_ctx; + struct cache_req_data *data; + + enum_ctx = sss_nss_setnetgrent_set_enum_ctx(table, netgroup); + if (enum_ctx == NULL) { + return NULL; + } + + data = cache_req_data_name(mem_ctx, type, netgroup); + if (data == NULL) { + return NULL; + } + + return sss_nss_setent_internal_send(mem_ctx, ev, cli_ctx, data, type, enum_ctx, + sss_nss_setnetgrent_set_timeout); +} diff --git a/src/responder/nss/nss_get_object.c b/src/responder/nss/nss_get_object.c new file mode 100644 index 0000000..29f9cb5 --- /dev/null +++ b/src/responder/nss/nss_get_object.c @@ -0,0 +1,546 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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 <tevent.h> +#include <talloc.h> + +#include "util/util.h" +#include "responder/nss/nss_private.h" +#include "responder/nss/nsssrv_mmap_cache.h" + +static errno_t +memcache_delete_entry_by_name(struct sss_nss_ctx *nss_ctx, + struct sized_string *name, + enum sss_mc_type type) +{ + errno_t ret; + + switch (type) { + case SSS_MC_PASSWD: + ret = sss_mmap_cache_pw_invalidate(&nss_ctx->pwd_mc_ctx, name); + break; + case SSS_MC_GROUP: + ret = sss_mmap_cache_gr_invalidate(&nss_ctx->grp_mc_ctx, name); + break; + case SSS_MC_INITGROUPS: + ret = sss_mmap_cache_initgr_invalidate(&nss_ctx->initgr_mc_ctx, name); + break; + default: + return EINVAL; + } + + if (ret == EOK || ret == ENOENT) { + return EOK; + } + + DEBUG(SSSDBG_CRIT_FAILURE, + "Internal failure in memory cache code: %d [%s]\n", + ret, sss_strerror(ret)); + + return ret; +} + +static errno_t +memcache_delete_entry_by_id(struct sss_nss_ctx *nss_ctx, + uint32_t id, + enum sss_mc_type type) +{ + errno_t ret; + + switch (type) { + case SSS_MC_PASSWD: + ret = sss_mmap_cache_pw_invalidate_uid(&nss_ctx->pwd_mc_ctx, (uid_t)id); + break; + case SSS_MC_GROUP: + ret = sss_mmap_cache_gr_invalidate_gid(&nss_ctx->grp_mc_ctx, (gid_t)id); + break; + default: + return EINVAL; + } + + if (ret == EOK || ret == ENOENT) { + return EOK; + } + + DEBUG(SSSDBG_CRIT_FAILURE, + "Internal failure in memory cache code: %d [%s]\n", + ret, sss_strerror(ret)); + + return ret; +} + +errno_t +memcache_delete_entry(struct sss_nss_ctx *nss_ctx, + struct resp_ctx *rctx, + struct sss_domain_info *domain, + const char *name, + uint32_t id, + enum sss_mc_type type) +{ + struct sss_domain_info *dom; + struct sized_string *sized_name; + errno_t ret; + + for (dom = rctx->domains; + dom != NULL; + dom = get_next_domain(dom, SSS_GND_DESCEND)) { + + if (domain == dom) { + /* We found entry in this domain so we don't + * wont to invalidate it here. */ + continue; + } + + if (name != NULL) { + ret = sized_output_name(NULL, rctx, name, dom, &sized_name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Unable to create sized name [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + ret = memcache_delete_entry_by_name(nss_ctx, sized_name, type); + talloc_zfree(sized_name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Unable to delete '%s' from domain '%s' memory cache!\n", + name, dom->name); + continue; + } + } else if (id == 0) { + /* + * As "root" is not handled by SSSD, let's just return EOK here + * instead of erroring out. + */ + return EOK; + } else if (id != 0) { + ret = memcache_delete_entry_by_id(nss_ctx, id, type); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Unable to delete '%u' from domain '%s' memory cache!\n", + id, dom->name); + continue; + } + } else { + DEBUG(SSSDBG_OP_FAILURE, "Bug: invalid input!"); + return ERR_INTERNAL; + } + } + + return EOK; +} + +static struct cache_req_data * +hybrid_domain_retry_data(TALLOC_CTX *mem_ctx, + struct cache_req_data *orig, + const char *input_name, + uint32_t input_id) +{ + enum cache_req_type cr_type = cache_req_data_get_type(orig); + struct cache_req_data *hybrid_data = NULL; + + if (cr_type == CACHE_REQ_GROUP_BY_ID) { + DEBUG(SSSDBG_TRACE_FUNC, + "Retrying group-by-ID lookup in user space\n"); + hybrid_data = cache_req_data_id(mem_ctx, + CACHE_REQ_USER_BY_ID, + input_id); + } else if (cr_type == CACHE_REQ_GROUP_BY_NAME) { + DEBUG(SSSDBG_TRACE_FUNC, + "Retrying group-by-name lookup in user space\n"); + hybrid_data = cache_req_data_name(mem_ctx, + CACHE_REQ_USER_BY_NAME, + input_name); + } + + if (hybrid_data != NULL) { + cache_req_data_set_hybrid_lookup(hybrid_data, true); + } + + return hybrid_data; +} + +static struct cache_req_data * +hybrid_domain_verify_gid_data(TALLOC_CTX *mem_ctx, + struct cache_req_result *user_group) +{ + gid_t gid; + + /* read the GID of this 'group' and use it to construct + * a cache_req_data struct + */ + gid = sss_view_ldb_msg_find_attr_as_uint64(user_group->domain, + user_group->msgs[0], + SYSDB_GIDNUM, + 0); + if (gid == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "A user with no GID?\n"); + return NULL; + } + + return cache_req_data_id(mem_ctx, + CACHE_REQ_GROUP_BY_ID, + gid); +} + +static int +hybrid_domain_user_to_group(struct cache_req_result *result) +{ + errno_t ret; + uid_t uid; + gid_t gid; + + /* There must be exactly one entry.. */ + if (result == NULL || result->count != 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "No result or wrong number of entries, expected 1 entry\n"); + return ENOENT; + } + + /* ...which has uidNumber equal to gidNumber */ + uid = sss_view_ldb_msg_find_attr_as_uint64(result->domain, + result->msgs[0], + SYSDB_UIDNUM, + 0); + + gid = sss_view_ldb_msg_find_attr_as_uint64(result->domain, + result->msgs[0], + SYSDB_GIDNUM, + 0); + + if (uid == 0 || uid != gid) { + DEBUG(SSSDBG_TRACE_INTERNAL, "UID and GID differ\n"); + return ENOENT; + } + + /* OK, we have a user with uid == gid; let's pretend this is a group */ + ret = ldb_msg_add_string(result->msgs[0], + SYSDB_OBJECTCATEGORY, + SYSDB_GROUP_CLASS); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Cannot add group class\n"); + return ret; + } + + return EOK; +} + +struct sss_nss_get_object_state { + struct sss_nss_ctx *nss_ctx; + struct resp_ctx *rctx; + struct tevent_context *ev; + struct cli_ctx *cli_ctx; + struct cache_req_data *data; + + /* We delete object from memory cache if it is not found */ + enum sss_mc_type memcache; + const char *input_name; + uint32_t input_id; + + struct cache_req_result *result; +}; + +static void sss_nss_get_object_done(struct tevent_req *subreq); +static bool sss_nss_is_hybrid_object_enabled(struct sss_domain_info *domains); +static errno_t sss_nss_get_hybrid_object_step(struct tevent_req *req); +static void sss_nss_get_hybrid_object_done(struct tevent_req *subreq); +static void sss_nss_get_hybrid_gid_verify_done(struct tevent_req *subreq); +static void sss_nss_get_object_finish_req(struct tevent_req *req, + errno_t ret); + +/* Cache request data memory context is stolen to internal state. */ +struct tevent_req * +sss_nss_get_object_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct cli_ctx *cli_ctx, + struct cache_req_data *data, + enum sss_mc_type memcache, + const char *input_name, + uint32_t input_id) +{ + struct sss_nss_get_object_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct sss_nss_get_object_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + state->ev = ev; + state->cli_ctx = cli_ctx; + state->data = talloc_steal(state, data); + + state->rctx = cli_ctx->rctx; + state->nss_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct sss_nss_ctx); + state->memcache = memcache; + state->input_id = input_id; + state->input_name = talloc_strdup(state, input_name); + if (input_name != NULL && state->input_name == NULL) { + ret = ENOMEM; + goto done; + } + + subreq = cache_req_send(req, ev, cli_ctx->rctx, cli_ctx->rctx->ncache, + state->nss_ctx->cache_refresh_percent, + CACHE_REQ_POSIX_DOM, NULL, data); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Client [%p][%d]: unable to send cache request!\n", + cli_ctx, cli_ctx->cfd); + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Client [%p][%d]: sent cache request #%u\n", + cli_ctx, cli_ctx->cfd, cache_req_get_reqid(subreq)); + + tevent_req_set_callback(subreq, sss_nss_get_object_done, req); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void sss_nss_get_object_done(struct tevent_req *subreq) +{ + struct sss_nss_get_object_state *state; + struct tevent_req *req; + errno_t ret; + errno_t retry_ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sss_nss_get_object_state); + + ret = cache_req_single_domain_recv(state, subreq, &state->result); + talloc_zfree(subreq); + + /* Try to process hybrid object if any domain enables it. This will issue a + * cache_req that will iterate only over domains with MPG_HYBRID. */ + if (ret == ENOENT + && sss_nss_is_hybrid_object_enabled(state->nss_ctx->rctx->domains)) { + retry_ret = sss_nss_get_hybrid_object_step(req); + if (retry_ret == EAGAIN) { + /* Retrying hybrid search */ + return; + } + /* Otherwise return the value of ret as returned from + * cache_req_single_domain_recv + */ + } + + sss_nss_get_object_finish_req(req, ret); + return; +} + +static void sss_nss_get_object_finish_req(struct tevent_req *req, + errno_t ret) +{ + struct sss_nss_get_object_state *state; + + state = tevent_req_data(req, struct sss_nss_get_object_state); + + switch (ret) { + case EOK: + tevent_req_done(req); + break; + case ENOENT: + if ((state->memcache != SSS_MC_NONE) && (state->memcache != SSS_MC_SID)) { + /* Delete entry from all domains. */ + memcache_delete_entry(state->nss_ctx, state->rctx, NULL, + state->input_name, state->input_id, + state->memcache); + } + + tevent_req_error(req, ENOENT); + break; + default: + tevent_req_error(req, ret); + break; + } +} + +static bool sss_nss_is_hybrid_object_enabled(struct sss_domain_info *domains) +{ + struct sss_domain_info *dom; + + for (dom = domains; dom != NULL; + dom = get_next_domain(dom, SSS_GND_DESCEND)) { + if (dom->mpg_mode == MPG_HYBRID) { + return true; + } + } + + return false; +} + +static errno_t sss_nss_get_hybrid_object_step(struct tevent_req *req) +{ + struct tevent_req *subreq; + struct sss_nss_get_object_state *state; + + state = tevent_req_data(req, struct sss_nss_get_object_state); + + state->data = hybrid_domain_retry_data(state, + state->data, + state->input_name, + state->input_id); + if (state->data == NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "This request cannot be retried\n"); + return EOK; + } + + subreq = cache_req_send(req, + state->ev, + state->cli_ctx->rctx, + state->cli_ctx->rctx->ncache, + state->nss_ctx->cache_refresh_percent, + CACHE_REQ_POSIX_DOM, + NULL, + state->data); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to send cache request!\n"); + return ENOMEM; + } + tevent_req_set_callback(subreq, sss_nss_get_hybrid_object_done, req); + + return EAGAIN; +} + +static void sss_nss_get_hybrid_object_done(struct tevent_req *subreq) +{ + struct sss_nss_get_object_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sss_nss_get_object_state); + + ret = cache_req_single_domain_recv(state, subreq, &state->result); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Converting user object to a group\n"); + ret = hybrid_domain_user_to_group(state->result); + if (ret != EOK) { + goto done; + } + + /* If the "group" was requested by name, we also must verify that + * no other group with this ID exists in any domain, otherwise + * we would have returned a private group that should be shadowed, + * this record would have been inserted into the memcache and then + * even getgrgid() would return this unexpected group + */ + if (cache_req_data_get_type(state->data) == CACHE_REQ_USER_BY_NAME) { + DEBUG(SSSDBG_TRACE_FUNC, "Will verify if MPG group is shadowed\n"); + talloc_zfree(state->data); + state->data = hybrid_domain_verify_gid_data(state, state->result); + if (state->data == NULL) { + sss_nss_get_object_finish_req(req, EINVAL); + return; + } + + subreq = cache_req_send(req, + state->ev, + state->cli_ctx->rctx, + state->cli_ctx->rctx->ncache, + state->nss_ctx->cache_refresh_percent, + CACHE_REQ_POSIX_DOM, + NULL, + state->data); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to send cache request!\n"); + tevent_req_error(req, ENOENT); + return; + } + tevent_req_set_callback(subreq, sss_nss_get_hybrid_gid_verify_done, req); + return; + } + +done: + sss_nss_get_object_finish_req(req, ret); + return; +} + +static void sss_nss_get_hybrid_gid_verify_done(struct tevent_req *subreq) +{ + struct sss_nss_get_object_state *state; + struct cache_req_result *real_gr_result; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sss_nss_get_object_state); + + ret = cache_req_single_domain_recv(state, subreq, &real_gr_result); + talloc_zfree(subreq); + if (ret == ENOENT) { + /* There is no real group with the same GID as the autogenerated + * one we were checking, so let's return the autogenerated one + */ + ret = EOK; + goto done; + } else if (ret == EOK) { + /* The autogenerated group is shadowed by a real one. Don't return + * anything. + */ + DEBUG(SSSDBG_TRACE_FUNC, + "A real entry would be shadowed by MPG entry\n"); + ret = ENOENT; + goto done; + } + +done: + sss_nss_get_object_finish_req(req, ret); + return; +} + +errno_t +sss_nss_get_object_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct cache_req_result **_result, + const char **_rawname) +{ + struct sss_nss_get_object_state *state; + state = tevent_req_data(req, struct sss_nss_get_object_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (_result != NULL) { + *_result = talloc_steal(mem_ctx, state->result); + } + + if (_rawname != NULL) { + *_rawname = talloc_steal(mem_ctx, state->input_name); + } + + return EOK; +} diff --git a/src/responder/nss/nss_iface.c b/src/responder/nss/nss_iface.c new file mode 100644 index 0000000..db743f8 --- /dev/null +++ b/src/responder/nss/nss_iface.c @@ -0,0 +1,242 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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 "responder/nss/nss_private.h" +#include "responder/nss/nss_iface.h" +#include "sss_iface/sss_iface_async.h" + +static void +sss_nss_update_initgr_memcache(struct sss_nss_ctx *nctx, + const char *fq_name, const char *domain, + int gnum, uint32_t *groups) +{ + TALLOC_CTX *tmp_ctx = NULL; + struct sss_domain_info *dom; + struct ldb_result *res; + struct sized_string *delete_name; + bool changed = false; + uint32_t id; + uint32_t gids[gnum]; + int ret; + int i, j; + + for (dom = nctx->rctx->domains; + dom; + dom = get_next_domain(dom, SSS_GND_DESCEND)) { + if (strcasecmp(dom->name, domain) == 0) { + break; + } + } + + if (dom == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Unknown domain (%s) requested by provider\n", domain); + return; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return; + } + + ret = sized_output_name(tmp_ctx, nctx->rctx, fq_name, dom, &delete_name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sized_output_name failed for '%s': %d [%s]\n", + fq_name, ret, sss_strerror(ret)); + goto done; + } + + ret = sysdb_initgroups(tmp_ctx, dom, fq_name, &res); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_initgroups() failed [%d][%s]\n", + ret, strerror(ret)); + goto done; + } + + /* copy, we need the original intact in case we need to invalidate + * all the original groups */ + memcpy(gids, groups, gnum * sizeof(uint32_t)); + + if (ret == ENOENT || res->count == 0) { + /* The user is gone. Invalidate the mc record */ + ret = sss_mmap_cache_pw_invalidate(&nctx->pwd_mc_ctx, delete_name); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Internal failure in memory cache code: %d [%s]\n", + ret, strerror(ret)); + } + + /* Also invalidate his groups */ + changed = true; + } else { + /* we skip the first entry, it's the user itself */ + for (i = 0; i < res->count; i++) { + id = ldb_msg_find_attr_as_uint(res->msgs[i], SYSDB_GIDNUM, 0); + if (id == 0) { + /* probably non-POSIX group, skip */ + continue; + } + for (j = 0; j < gnum; j++) { + if (gids[j] == id) { + gids[j] = 0; + break; + } + } + if (j >= gnum) { + /* we couldn't find a match, this means the groups have + * changed after the refresh */ + changed = true; + break; + } + } + + if (!changed) { + for (j = 0; j < gnum; j++) { + if (gids[j] != 0) { + /* we found an un-cleared groups, this means the groups + * have changed after the refresh (some got deleted) */ + changed = true; + break; + } + } + } + } + + if (changed) { + for (i = 0; i < gnum; i++) { + id = groups[i]; + + ret = sss_mmap_cache_gr_invalidate_gid(&nctx->grp_mc_ctx, id); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Internal failure in memory cache code: %d [%s]\n", + ret, strerror(ret)); + } + } + + to_sized_string(delete_name, fq_name); + ret = sss_mmap_cache_initgr_invalidate(&nctx->initgr_mc_ctx, + delete_name); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Internal failure in memory cache code: %d [%s]\n", + ret, strerror(ret)); + } + } + +done: + talloc_free(tmp_ctx); +} + +static errno_t +sss_nss_memorycache_invalidate_users(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct sss_nss_ctx *nctx) +{ + DEBUG(SSSDBG_TRACE_LIBS, "Invalidating all users in memory cache\n"); + sss_mmap_cache_reset(nctx->pwd_mc_ctx); + + return EOK; +} + +static errno_t +sss_nss_memorycache_invalidate_groups(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct sss_nss_ctx *nctx) +{ + DEBUG(SSSDBG_TRACE_LIBS, "Invalidating all groups in memory cache\n"); + sss_mmap_cache_reset(nctx->grp_mc_ctx); + + return EOK; +} + +static errno_t +sss_nss_memorycache_invalidate_initgroups(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct sss_nss_ctx *nctx) +{ + DEBUG(SSSDBG_TRACE_LIBS, + "Invalidating all initgroup records in memory cache\n"); + sss_mmap_cache_reset(nctx->initgr_mc_ctx); + + return EOK; +} + +static errno_t +sss_nss_memorycache_update_initgroups(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct sss_nss_ctx *nctx, + const char *user, + const char *domain, + uint32_t *groups) +{ + DEBUG(SSSDBG_TRACE_LIBS, "Updating initgroups memory cache of [%s@%s]\n", + user, domain); + + sss_nss_update_initgr_memcache(nctx, user, domain, + talloc_array_length(groups), groups); + + return EOK; +} + +static errno_t +sss_nss_memorycache_invalidate_group_by_id(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct sss_nss_ctx *nctx, + uint32_t gid) +{ + + DEBUG(SSSDBG_TRACE_LIBS, + "Invalidating group %u from memory cache\n", gid); + + sss_mmap_cache_gr_invalidate_gid(&nctx->grp_mc_ctx, gid); + + return EOK; +} + +errno_t +sss_nss_register_backend_iface(struct sbus_connection *conn, + struct sss_nss_ctx *nss_ctx) +{ + errno_t ret; + + SBUS_INTERFACE(iface, + sssd_nss_MemoryCache, + SBUS_METHODS( + SBUS_SYNC(METHOD, sssd_nss_MemoryCache, UpdateInitgroups, sss_nss_memorycache_update_initgroups, nss_ctx), + SBUS_SYNC(METHOD, sssd_nss_MemoryCache, InvalidateAllUsers, sss_nss_memorycache_invalidate_users, nss_ctx), + SBUS_SYNC(METHOD, sssd_nss_MemoryCache, InvalidateAllGroups, sss_nss_memorycache_invalidate_groups, nss_ctx), + SBUS_SYNC(METHOD, sssd_nss_MemoryCache, InvalidateAllInitgroups, sss_nss_memorycache_invalidate_initgroups, nss_ctx), + SBUS_SYNC(METHOD, sssd_nss_MemoryCache, InvalidateGroupById, sss_nss_memorycache_invalidate_group_by_id, nss_ctx) + ), + SBUS_SIGNALS(SBUS_NO_SIGNALS), + SBUS_PROPERTIES(SBUS_NO_PROPERTIES) + ); + + ret = sbus_connection_add_path(conn, SSS_BUS_PATH, &iface); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to register service interface" + "[%d]: %s\n", ret, sss_strerror(ret)); + } + + return ret; +} diff --git a/src/responder/nss/nss_iface.h b/src/responder/nss/nss_iface.h new file mode 100644 index 0000000..8aabddb --- /dev/null +++ b/src/responder/nss/nss_iface.h @@ -0,0 +1,31 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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/>. +*/ + +#ifndef _NSS_IFACE_H_ +#define _NSS_IFACE_H_ + +#include "sss_iface/sss_iface_async.h" +#include "responder/nss/nss_private.h" + +errno_t +sss_nss_register_backend_iface(struct sbus_connection *conn, + struct sss_nss_ctx *nss_ctx); + +#endif /* _NSS_IFACE_H_ */ diff --git a/src/responder/nss/nss_private.h b/src/responder/nss/nss_private.h new file mode 100644 index 0000000..e2f5a3e --- /dev/null +++ b/src/responder/nss/nss_private.h @@ -0,0 +1,155 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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/>. +*/ + +#ifndef _NSS_PRIVATE_H_ +#define _NSS_PRIVATE_H_ + +#include <talloc.h> +#include <tevent.h> +#include <dhash.h> +#include <ldb.h> + +#include "util/util.h" +#include "db/sysdb.h" +#include "responder/common/responder.h" +#include "responder/common/cache_req/cache_req.h" +#include "responder/nss/nsssrv_mmap_cache.h" +#include "lib/idmap/sss_idmap.h" + +struct sss_nss_enum_index { + unsigned int domain; + unsigned int result; +}; + +struct sss_nss_enum_ctx { + struct cache_req_result **result; + struct sysdb_netgroup_ctx **netgroup; + size_t netgroup_count; + + /* Ongoing cache request that is constructing enumeration result. */ + struct tevent_req *ongoing; + + /* If true, the object is already constructed. */ + bool is_ready; + + /* List of setent requests awaiting the result. We finish + * them when the ongoing cache request is completed. */ + struct setent_req_list *notify_list; +}; + +struct sss_nss_state_ctx { + struct sss_nss_enum_index pwent; + struct sss_nss_enum_index grent; + struct sss_nss_enum_index svcent; + struct sss_nss_enum_index netgrent; + struct sss_nss_enum_index hostent; + struct sss_nss_enum_index netent; + + const char *netgroup; +}; + +struct sss_nss_ctx { + struct resp_ctx *rctx; + struct sss_idmap_ctx *idmap_ctx; + + /* Options. */ + int cache_refresh_percent; + int enum_cache_timeout; + bool filter_users_in_groups; + char *pwfield; + char *override_homedir; + char *fallback_homedir; + char *homedir_substr; + const char **extra_attributes; + const char **full_attribute_list; + + /* Enumeration. */ + struct sss_nss_enum_ctx *pwent; + struct sss_nss_enum_ctx *grent; + struct sss_nss_enum_ctx *svcent; + struct sss_nss_enum_ctx *hostent; + struct sss_nss_enum_ctx *netent; + hash_table_t *netgrent; + + /* Memory cache. */ + struct sss_mc_ctx *pwd_mc_ctx; + struct sss_mc_ctx *grp_mc_ctx; + struct sss_mc_ctx *initgr_mc_ctx; + struct sss_mc_ctx *sid_mc_ctx; + uid_t mc_uid; + gid_t mc_gid; +}; + +struct sss_cmd_table *get_sss_nss_cmds(void); + +int sss_nss_connection_setup(struct cli_ctx *cli_ctx); + +errno_t +memcache_delete_entry(struct sss_nss_ctx *nss_ctx, + struct resp_ctx *rctx, + struct sss_domain_info *domain, + const char *name, + uint32_t id, + enum sss_mc_type type); + +struct tevent_req * +sss_nss_get_object_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct cli_ctx *cli_ctx, + struct cache_req_data *data, + enum sss_mc_type memcache, + const char *input_name, + uint32_t input_id); + +errno_t +sss_nss_get_object_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct cache_req_result **_result, + const char **_rawname); + +struct tevent_req * +sss_nss_setent_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct cli_ctx *cli_ctx, + enum cache_req_type type, + struct sss_nss_enum_ctx *enum_ctx); + +errno_t +sss_nss_setent_recv(struct tevent_req *req); + +struct tevent_req * +sss_nss_setnetgrent_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct cli_ctx *cli_ctx, + enum cache_req_type type, + hash_table_t *table, + const char *netgroup); + +/* Utils. */ + +const char * +sss_nss_get_name_from_msg(struct sss_domain_info *domain, + struct ldb_message *msg); + +const char * +sss_nss_get_pwfield(struct sss_nss_ctx *nctx, + struct sss_domain_info *dom); + +#endif /* _NSS_PRIVATE_H_ */ diff --git a/src/responder/nss/nss_protocol.c b/src/responder/nss/nss_protocol.c new file mode 100644 index 0000000..e6dc702 --- /dev/null +++ b/src/responder/nss/nss_protocol.c @@ -0,0 +1,487 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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 "util/util.h" +#include "util/cert.h" +#include "lib/idmap/sss_idmap.h" +#include "responder/nss/nss_protocol.h" +#include <arpa/inet.h> + +errno_t +sss_nss_protocol_done(struct cli_ctx *cli_ctx, errno_t error) +{ + struct cli_protocol *pctx; + errno_t ret; + + pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol); + + switch (error) { + case EOK: + /* Create empty packet if none was provided. */ + if (pctx->creq->out == NULL) { + ret = sss_packet_new(pctx->creq, 0, + sss_packet_get_cmd(pctx->creq->in), + &pctx->creq->out); + if (ret != EOK) { + goto done; + } + + sss_packet_set_error(pctx->creq->out, EOK); + } + + DEBUG(SSSDBG_TRACE_ALL, "Sending reply: success\n"); + ret = EOK; + goto done; + case ENOENT: + DEBUG(SSSDBG_TRACE_ALL, "Sending reply: not found\n"); + ret = sss_cmd_send_empty(cli_ctx); + goto done; + default: + DEBUG(SSSDBG_TRACE_ALL, "Sending reply: error [%d]: %s\n", + error, sss_strerror(error)); + ret = sss_cmd_send_error(cli_ctx, error); + goto done; + } + +done: + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to send reply [%d]: %s!\n", + ret, sss_strerror(ret)); + return ret; + } + + sss_cmd_done(cli_ctx, NULL); + return EOK; +} + +void sss_nss_protocol_reply(struct cli_ctx *cli_ctx, + struct sss_nss_ctx *nss_ctx, + struct sss_nss_cmd_ctx *cmd_ctx, + struct cache_req_result *result, + sss_nss_protocol_fill_packet_fn fill_fn) +{ + struct cli_protocol *pctx; + errno_t ret; + + pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol); + + ret = sss_packet_new(pctx->creq, 0, sss_packet_get_cmd(pctx->creq->in), + &pctx->creq->out); + if (ret != EOK) { + goto done; + } + + ret = fill_fn(nss_ctx, cmd_ctx, pctx->creq->out, result); + if (ret != EOK) { + goto done; + } + + sss_packet_set_error(pctx->creq->out, EOK); + +done: + sss_nss_protocol_done(cli_ctx, ret); +} + +errno_t +sss_nss_protocol_parse_name(struct cli_ctx *cli_ctx, const char **_rawname) +{ + struct cli_protocol *pctx; + const char *rawname; + uint8_t *body; + size_t blen; + + pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol); + + sss_packet_get_body(pctx->creq->in, &body, &blen); + + /* If not terminated fail. */ + if (body[blen - 1] != '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, "Body is not null terminated!\n"); + return EINVAL; + } + + /* If the body isn't valid UTF-8, fail */ + if (!sss_utf8_check(body, blen - 1)) { + DEBUG(SSSDBG_CRIT_FAILURE, "Body is not UTF-8 string!\n"); + return EINVAL; + } + + rawname = (const char *)body; + if (rawname[0] == '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, "An empty name was provided!\n"); + return EINVAL; + } + + *_rawname = rawname; + + return EOK; +} + +errno_t +sss_nss_protocol_parse_name_ex(struct cli_ctx *cli_ctx, const char **_rawname, + uint32_t *_flags) +{ + struct cli_protocol *pctx; + const char *rawname; + uint8_t *body; + size_t blen; + uint8_t *p; + uint32_t flags; + + pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol); + + sss_packet_get_body(pctx->creq->in, &body, &blen); + + if (blen < 1 + sizeof(uint32_t)) { + DEBUG(SSSDBG_CRIT_FAILURE, "Body too short!\n"); + return EINVAL; + } + + /* If first argument not terminated fail. */ + if (body[blen - 1 - sizeof(uint32_t)] != '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, "Body is not null terminated!\n"); + return EINVAL; + } + + p = memchr(body, '\0', blen); + /* Although body for sure is null terminated, let's add this check here + * so static analyzers are happier. */ + if (p == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "memchr() returned NULL, body is not null terminated!\n"); + return EINVAL; + } + + /* If the body isn't valid UTF-8, fail */ + if (!sss_utf8_check(body, (p - body))) { + DEBUG(SSSDBG_CRIT_FAILURE, "First argument is not UTF-8 string!\n"); + return EINVAL; + } + + rawname = (const char *)body; + if (rawname[0] == '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, "An empty name was provided!\n"); + return EINVAL; + } + + p++; + if ((p - body) + sizeof(uint32_t) != blen) { + DEBUG(SSSDBG_CRIT_FAILURE, "Body has unexpected size!\n"); + return EINVAL; + } + + SAFEALIGN_COPY_UINT32(&flags, p, NULL); + + *_rawname = rawname; + *_flags = flags; + + return EOK; +} + +errno_t +sss_nss_protocol_parse_id(struct cli_ctx *cli_ctx, uint32_t *_id) +{ + struct cli_protocol *pctx; + uint8_t *body; + size_t blen; + uint32_t id; + + pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol); + + sss_packet_get_body(pctx->creq->in, &body, &blen); + + if (blen != sizeof(uint32_t)) { + return EINVAL; + } + + SAFEALIGN_COPY_UINT32(&id, body, NULL); + + *_id = id; + + return EOK; +} + +errno_t +sss_nss_protocol_parse_id_ex(struct cli_ctx *cli_ctx, uint32_t *_id, + uint32_t *_flags) +{ + struct cli_protocol *pctx; + uint8_t *body; + size_t blen; + uint32_t id; + uint32_t flags; + + pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol); + + sss_packet_get_body(pctx->creq->in, &body, &blen); + + if (blen != 2 * sizeof(uint32_t)) { + return EINVAL; + } + + SAFEALIGN_COPY_UINT32(&id, body, NULL); + SAFEALIGN_COPY_UINT32(&flags, body + sizeof(uint32_t), NULL); + + *_id = id; + *_flags = flags; + + return EOK; +} + +errno_t +sss_nss_protocol_parse_limit(struct cli_ctx *cli_ctx, uint32_t *_limit) +{ + return sss_nss_protocol_parse_id(cli_ctx, _limit); +} + +errno_t +sss_nss_protocol_parse_svc_name(struct cli_ctx *cli_ctx, + const char **_name, + const char **_protocol) +{ + struct cli_protocol *pctx; + const char *protocol; + const char *name; + size_t protocol_len; + size_t name_len; + uint8_t *body; + size_t blen; + int i; + + pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol); + + sss_packet_get_body(pctx->creq->in, &body, &blen); + + /* If not terminated fail. */ + if (body[blen - 1] != '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, "Body is not null terminated\n"); + return EINVAL; + } + + /* Calculate service name length. */ + for (i = 0, name_len = 0; body[i] != '\0'; i++) { + name_len++; + } + + /* Calculate protocol name length, use index from previous cycle. */ + for (protocol_len = 0; body[i + 1] != '\0'; i++) { + protocol_len++; + } + + if (name_len == 0) { + return EINVAL; + } + + name = (const char *)body; + protocol = protocol_len == 0 ? NULL : (const char *)(body + name_len + 1); + + if (!sss_utf8_check((const uint8_t *)name, name_len)) { + DEBUG(SSSDBG_CRIT_FAILURE, "Name is not UTF-8 string\n"); + return EINVAL; + } + + if (!sss_utf8_check((const uint8_t *)protocol, protocol_len)) { + DEBUG(SSSDBG_CRIT_FAILURE, "Protocol is not UTF-8 string\n"); + return EINVAL; + } + + *_name = name; + *_protocol = protocol; + + return EOK; +} + +errno_t +sss_nss_protocol_parse_svc_port(struct cli_ctx *cli_ctx, + uint16_t *_port, + const char **_protocol) +{ + struct cli_protocol *pctx; + const char *protocol; + size_t protocol_len; + uint16_t port; + uint8_t *body; + size_t blen; + int i; + + pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol); + + sss_packet_get_body(pctx->creq->in, &body, &blen); + + /* If not terminated fail. */ + if (body[blen - 1] != '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, "Body is not null terminated\n"); + return EINVAL; + } + + SAFEALIGN_COPY_UINT16(&port, body, NULL); + port = ntohs(port); + + /* Move behind the port and padding to get the protocol. */ + body = body + 2 * sizeof(uint16_t) + sizeof(uint32_t); + + /* Calculate protocol name length. */ + for (protocol_len = 0, i = 0; body[i] != '\0'; i++) { + protocol_len++; + } + + protocol = protocol_len == 0 ? NULL : (const char *)body; + + if (!sss_utf8_check((const uint8_t *)protocol, protocol_len)) { + DEBUG(SSSDBG_CRIT_FAILURE, "Protocol is not UTF-8 string\n"); + return EINVAL; + } + + *_port = port; + *_protocol = protocol; + + return EOK; +} + +errno_t +sss_nss_protocol_parse_cert(struct cli_ctx *cli_ctx, + const char **_derb64) +{ + struct cli_protocol *pctx; + const char *derb64; + size_t pem_size; + char *pem_cert; + uint8_t *body; + size_t blen; + errno_t ret; + + pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol); + + sss_packet_get_body(pctx->creq->in, &body, &blen); + + /* If not terminated fail. */ + if (body[blen - 1] != '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, "Body is not null terminated\n"); + return EINVAL; + } + + derb64 = (const char *)body; + + DEBUG(SSSDBG_TRACE_ALL, "Input certificate [%s]\n", derb64); + + /* Check input. */ + ret = sss_cert_derb64_to_pem(cli_ctx, derb64, &pem_cert, &pem_size); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Unable to convert certificate to pem [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + talloc_free(pem_cert); + + *_derb64 = derb64; + + return EOK; +} + +errno_t +sss_nss_protocol_parse_sid(struct cli_ctx *cli_ctx, + const char **_sid) +{ + struct cli_protocol *pctx; + struct sss_nss_ctx *nss_ctx; + const char *sid; + uint8_t *bin_sid; + size_t bin_len; + uint8_t *body; + size_t blen; + enum idmap_error_code err; + + pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol); + nss_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct sss_nss_ctx); + + sss_packet_get_body(pctx->creq->in, &body, &blen); + + /* If not terminated fail. */ + if (body[blen - 1] != '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, "Body is not null terminated\n"); + return EINVAL; + } + + sid = (const char *)body; + + /* If the body isn't a SID, fail */ + err = sss_idmap_sid_to_bin_sid(nss_ctx->idmap_ctx, sid, &bin_sid, + &bin_len); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, + "Unable to convert SID to binary [%s].\n", sid); + return EINVAL; + } + + sss_idmap_free_bin_sid(nss_ctx->idmap_ctx, bin_sid); + + DEBUG(SSSDBG_TRACE_ALL, "Input SID [%s]\n", sid); + + *_sid = sid; + + return EOK; +} + +errno_t +sss_nss_protocol_parse_addr(struct cli_ctx *cli_ctx, + uint32_t *_af, + uint32_t *_addrlen, + uint8_t **_addr) +{ + struct cli_protocol *pctx; + uint8_t *body; + size_t blen; + uint32_t af; + uint8_t *addr; + socklen_t addrlen; + char buf[INET6_ADDRSTRLEN]; + const char *addrstr = NULL; + + pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol); + + sss_packet_get_body(pctx->creq->in, &body, &blen); + + if (blen < sizeof(uint32_t) * 2) { + return EINVAL; + } + + SAFEALIGN_COPY_UINT32(&af, body, NULL); + SAFEALIGN_COPY_UINT32(&addrlen, body + sizeof(uint32_t), NULL); + + addr = body + sizeof(uint32_t) * 2; + + /* If the body isn't a addr, fail */ + addrstr = inet_ntop(af, addr, buf, INET6_ADDRSTRLEN); + if (addrstr == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to parse address: %s\n", strerror(errno)); + return EINVAL; + } + + DEBUG(SSSDBG_TRACE_ALL, "Input address [%s]\n", addrstr); + + *_af = af; + *_addr = addr; + *_addrlen = addrlen; + + return EOK; +} diff --git a/src/responder/nss/nss_protocol.h b/src/responder/nss/nss_protocol.h new file mode 100644 index 0000000..13ff870 --- /dev/null +++ b/src/responder/nss/nss_protocol.h @@ -0,0 +1,217 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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/>. +*/ + +#ifndef _NSS_PROTOCOL_H_ +#define _NSS_PROTOCOL_H_ + +#include <stdint.h> + +#include "util/util.h" +#include "responder/common/responder.h" +#include "responder/common/responder_packet.h" +#include "responder/common/cache_req/cache_req.h" +#include "responder/nss/nss_private.h" +#include "sss_client/idmap/sss_nss_idmap.h" + +struct sss_nss_cmd_ctx; + +/** + * Fill SSSD response packet. + * + * @return EOK If packet is successfully created and should be sent to client. + * @return Other errno code on error, an error reply will be sent to client. + */ +typedef errno_t +(*sss_nss_protocol_fill_packet_fn)(struct sss_nss_ctx *nss_ctx, + struct sss_nss_cmd_ctx *cmd_ctx, + struct sss_packet *packet, + struct cache_req_result *result); + +struct sss_nss_cmd_ctx { + enum cache_req_type type; + struct cli_ctx *cli_ctx; + struct sss_nss_ctx *nss_ctx; + struct sss_nss_state_ctx *state_ctx; + sss_nss_protocol_fill_packet_fn fill_fn; + uint32_t flags; + + /* For initgroups- */ + const char *rawname; + + /* For enumeration. */ + bool enumeration; + struct sss_nss_enum_ctx *enum_ctx; + struct sss_nss_enum_index *enum_index; + uint32_t enum_limit; + + /* For services. */ + const char *svc_protocol; + + /* For SID lookups. */ + enum sss_id_type sid_id_type; +}; + +/** + * If error is EOK, send existing reply packet to the client. + * If error is ENOENT, create and send empty response. + * On other error code, create and send an error. + */ +errno_t sss_nss_protocol_done(struct cli_ctx *cli_ctx, errno_t error); + +/** + * Create and send SSSD response packet to the client. + */ +void sss_nss_protocol_reply(struct cli_ctx *cli_ctx, + struct sss_nss_ctx *nss_ctx, + struct sss_nss_cmd_ctx *cmd_ctx, + struct cache_req_result *result, + sss_nss_protocol_fill_packet_fn fill_fn); + +/* Parse input packet. */ + +errno_t +sss_nss_protocol_parse_name(struct cli_ctx *cli_ctx, const char **_rawname); + +errno_t +sss_nss_protocol_parse_name_ex(struct cli_ctx *cli_ctx, const char **_rawname, + uint32_t *_flags); + +errno_t +sss_nss_protocol_parse_id(struct cli_ctx *cli_ctx, uint32_t *_id); + +errno_t +sss_nss_protocol_parse_id_ex(struct cli_ctx *cli_ctx, uint32_t *_id, + uint32_t *_flags); + +errno_t +sss_nss_protocol_parse_limit(struct cli_ctx *cli_ctx, uint32_t *_limit); + +errno_t +sss_nss_protocol_parse_svc_name(struct cli_ctx *cli_ctx, + const char **_name, + const char **_protocol); + +errno_t +sss_nss_protocol_parse_svc_port(struct cli_ctx *cli_ctx, + uint16_t *_port, + const char **_protocol); + +errno_t +sss_nss_protocol_parse_cert(struct cli_ctx *cli_ctx, + const char **_derb64); + +errno_t +sss_nss_protocol_parse_sid(struct cli_ctx *cli_ctx, + const char **_sid); + +errno_t +sss_nss_protocol_parse_addr(struct cli_ctx *cli_ctx, + uint32_t *_af, + uint32_t *_addrlen, + uint8_t **_addr); + +/* Create response packet. */ + +errno_t +sss_nss_protocol_fill_pwent(struct sss_nss_ctx *nss_ctx, + struct sss_nss_cmd_ctx *cmd_ctx, + struct sss_packet *packet, + struct cache_req_result *result); + +errno_t +sss_nss_protocol_fill_grent(struct sss_nss_ctx *nss_ctx, + struct sss_nss_cmd_ctx *cmd_ctx, + struct sss_packet *packet, + struct cache_req_result *result); + +errno_t +sss_nss_protocol_fill_initgr(struct sss_nss_ctx *nss_ctx, + struct sss_nss_cmd_ctx *cmd_ctx, + struct sss_packet *packet, + struct cache_req_result *result); + +#ifdef BUILD_SUBID +errno_t +sss_nss_protocol_fill_subid_ranges(struct sss_nss_ctx *sss_nss_ctx, + struct sss_nss_cmd_ctx *cmd_ctx, + struct sss_packet *packet, + struct cache_req_result *result); +#endif + +errno_t +sss_nss_protocol_fill_netgrent(struct sss_nss_ctx *nss_ctx, + struct sss_nss_cmd_ctx *cmd_ctx, + struct sss_packet *packet, + struct cache_req_result *result); + +errno_t +sss_nss_protocol_fill_svcent(struct sss_nss_ctx *nss_ctx, + struct sss_nss_cmd_ctx *cmd_ctx, + struct sss_packet *packet, + struct cache_req_result *result); + +errno_t +sss_nss_protocol_fill_sid(struct sss_nss_ctx *nss_ctx, + struct sss_nss_cmd_ctx *cmd_ctx, + struct sss_packet *packet, + struct cache_req_result *result); + +errno_t +sss_nss_protocol_fill_orig(struct sss_nss_ctx *nss_ctx, + struct sss_nss_cmd_ctx *cmd_ctx, + struct sss_packet *packet, + struct cache_req_result *result); + +errno_t +sss_nss_protocol_fill_name(struct sss_nss_ctx *nss_ctx, + struct sss_nss_cmd_ctx *cmd_ctx, + struct sss_packet *packet, + struct cache_req_result *result); + +errno_t +sss_nss_protocol_fill_single_name(struct sss_nss_ctx *nss_ctx, + struct sss_nss_cmd_ctx *cmd_ctx, + struct sss_packet *packet, + struct cache_req_result *result); + +errno_t +sss_nss_protocol_fill_name_list_all_domains(struct sss_nss_ctx *nss_ctx, + struct sss_nss_cmd_ctx *cmd_ctx, + struct sss_packet *packet, + struct cache_req_result **results); + +errno_t +sss_nss_protocol_fill_id(struct sss_nss_ctx *nss_ctx, + struct sss_nss_cmd_ctx *cmd_ctx, + struct sss_packet *packet, + struct cache_req_result *result); + +errno_t +sss_nss_protocol_fill_hostent(struct sss_nss_ctx *nss_ctx, + struct sss_nss_cmd_ctx *cmd_ctx, + struct sss_packet *packet, + struct cache_req_result *result); +errno_t +sss_nss_protocol_fill_netent(struct sss_nss_ctx *nss_ctx, + struct sss_nss_cmd_ctx *cmd_ctx, + struct sss_packet *packet, + struct cache_req_result *result); + +#endif /* _NSS_PROTOCOL_H_ */ diff --git a/src/responder/nss/nss_protocol_grent.c b/src/responder/nss/nss_protocol_grent.c new file mode 100644 index 0000000..887501a --- /dev/null +++ b/src/responder/nss/nss_protocol_grent.c @@ -0,0 +1,495 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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 "responder/nss/nss_protocol.h" +#include "util/sss_format.h" + +static errno_t +sss_nss_get_grent(TALLOC_CTX *mem_ctx, + struct sss_nss_ctx *nss_ctx, + struct sss_domain_info *domain, + struct ldb_message *msg, + uint32_t *_gid, + struct sized_string **_name) +{ + const char *name; + uint32_t gid; + errno_t ret; + + /* Check object class. */ + if (!ldb_msg_check_string_attribute(msg, SYSDB_OBJECTCATEGORY, + SYSDB_GROUP_CLASS)) { + DEBUG(SSSDBG_MINOR_FAILURE, "Wrong object (%s) found on stack!\n", + ldb_dn_get_linearized(msg->dn)); + return ERR_INTERNAL; + } + + /* Get fields. */ + name = sss_get_name_from_msg(domain, msg); + gid = sss_view_ldb_msg_find_attr_as_uint64(domain, msg, SYSDB_GIDNUM, 0); + + if (name == NULL || gid == 0) { + DEBUG(SSSDBG_OP_FAILURE, + "Incomplete group object for %s[%u]! Skipping\n", + name ? name : "<NULL>", gid); + return EINVAL; + } + + /* Convert to sized strings. */ + ret = sized_output_name(mem_ctx, nss_ctx->rctx, name, domain, _name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sized_output_name failed, skipping [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + *_gid = gid; + + return EOK; +} + +static struct ldb_message_element * +sss_nss_get_group_members(struct sss_domain_info *domain, + struct ldb_message *msg) +{ + struct ldb_message_element *el; + + if (domain->ignore_group_members) { + return NULL; + } + + /* Unconditionally prefer OVERRIDE_PREFIX SYSDB_MEMBERUID, it + * might contain override names from the default view. */ + el = ldb_msg_find_element(msg, OVERRIDE_PREFIX SYSDB_MEMBERUID); + if (el == NULL) { + el = ldb_msg_find_element(msg, SYSDB_MEMBERUID); + } + + return el; +} + +static struct ldb_message_element * +sss_nss_get_group_ghosts(struct sss_domain_info *domain, + struct ldb_message *msg, + const char *group_name) +{ + struct ldb_message_element *el; + + if (domain->ignore_group_members) { + return NULL; + } + + el = ldb_msg_find_element(msg, SYSDB_GHOST); + if (el == NULL) { + return NULL; + } + + if (DOM_HAS_VIEWS(domain) && !is_local_view(domain->view_name) + && el->num_values != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Domain has a view [%s] but group [%s] still has " + "ghost members.\n", domain->view_name, group_name); + return NULL; + } + + return el; +} + +static errno_t +sss_nss_protocol_fill_members(struct sss_packet *packet, + struct sss_nss_ctx *nss_ctx, + struct sss_domain_info *domain, + struct ldb_message *msg, + const char *group_name, + size_t *_rp, + uint32_t *_num_members) +{ + TALLOC_CTX *tmp_ctx; + struct resp_ctx *rctx = nss_ctx->rctx; + struct ldb_message_element *members[2]; + struct ldb_message_element *el; + struct sized_string *name; + const char *member_name; + uint32_t num_members = 0; + size_t body_len; + uint8_t *body; + errno_t ret; + int i, j; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + members[0] = sss_nss_get_group_members(domain, msg); + members[1] = sss_nss_get_group_ghosts(domain, msg, group_name); + + if (is_files_provider(domain) && members[1] != NULL) { + /* If there is a ghost member in files provider it means that we + * did not store the user on purpose (e.g. it has uid or gid 0). + * Therefore nss_files does handle the user and therefore we + * must let nss_files to also handle this group in order to + * provide correct membership. */ + DEBUG(SSSDBG_TRACE_FUNC, + "Unknown members found. nss_files will handle it.\n"); + + ret = sss_ncache_set_group(rctx->ncache, false, domain, group_name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_ncache_set_group failed.\n"); + } + + ret = ENOENT; + goto done; + } + + sss_packet_get_body(packet, &body, &body_len); + + for (i = 0; i < sizeof(members) / sizeof(members[0]); i++) { + el = members[i]; + if (el == NULL) { + continue; + } + + for (j = 0; j < el->num_values; j++) { + member_name = (const char *)el->values[j].data; + + if (nss_ctx->filter_users_in_groups) { + ret = sss_ncache_check_user(rctx->ncache, domain, member_name); + if (ret == EEXIST) { + DEBUG(SSSDBG_TRACE_FUNC, + "Group [%s] member [%s] filtered out! " + "(negative cache)\n", group_name, member_name); + continue; + } + } + + ret = sized_domain_name(tmp_ctx, rctx, member_name, &name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to get sized name [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = sss_packet_grow(packet, name->len); + if (ret != EOK) { + goto done; + } + + sss_packet_get_body(packet, &body, &body_len); + SAFEALIGN_SET_STRING(&body[*_rp], name->str, name->len, _rp); + + num_members++; + } + } + + ret = EOK; + +done: + *_num_members = num_members; + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +sss_nss_protocol_fill_grent(struct sss_nss_ctx *nss_ctx, + struct sss_nss_cmd_ctx *cmd_ctx, + struct sss_packet *packet, + struct cache_req_result *result) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_message *msg; + struct sized_string *name; + struct sized_string pwfield; + uint32_t gid; + uint32_t num_results; + uint32_t num_members; + char *members; + size_t members_size; + size_t rp; + size_t rp_members; + size_t rp_num_members; + size_t body_len; + uint8_t *body; + int i; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + /* First two fields (length and reserved), filled up later. */ + ret = sss_packet_grow(packet, 2 * sizeof(uint32_t)); + if (ret != EOK) { + return ret; + } + + rp = 2 * sizeof(uint32_t); + + num_results = 0; + for (i = 0; i < result->count; i++) { + talloc_free_children(tmp_ctx); + msg = result->msgs[i]; + + /* Password field content. */ + to_sized_string(&pwfield, sss_nss_get_pwfield(nss_ctx, result->domain)); + + ret = sss_nss_get_grent(tmp_ctx, nss_ctx, result->domain, msg, + &gid, &name); + if (ret != EOK) { + continue; + } + + /* Adjust packet size: gid, num_members + string fields. */ + + ret = sss_packet_grow(packet, 2 * sizeof(uint32_t) + + name->len + pwfield.len); + if (ret != EOK) { + goto done; + } + + sss_packet_get_body(packet, &body, &body_len); + + /* Fill packet. */ + + SAFEALIGN_SET_UINT32(&body[rp], gid, &rp); + + /* Remember pointer to number of members field. */ + rp_num_members = rp; + SAFEALIGN_SET_UINT32(&body[rp], 0, &rp); + SAFEALIGN_SET_STRING(&body[rp], name->str, name->len, &rp); + SAFEALIGN_SET_STRING(&body[rp], pwfield.str, pwfield.len, &rp); + rp_members = rp; + + /* Fill members. */ + ret = sss_nss_protocol_fill_members(packet, nss_ctx, result->domain, msg, + name->str, &rp, &num_members); + if (ret != EOK) { + goto done; + } + + sss_packet_get_body(packet, &body, &body_len); + SAFEALIGN_SET_UINT32(&body[rp_num_members], num_members, NULL); + + num_results++; + + /* Do not store entry in memory cache during enumeration or when + * requested or if cache explicitly disabled. */ + if (!cmd_ctx->enumeration + && ((cmd_ctx->flags & SSS_NSS_EX_FLAG_INVALIDATE_CACHE) == 0) + && (nss_ctx->grp_mc_ctx != NULL)) { + members = (char *)&body[rp_members]; + members_size = body_len - rp_members; + ret = sss_mmap_cache_gr_store(&nss_ctx->grp_mc_ctx, name, &pwfield, + gid, num_members, members, + members_size); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to store group %s (%s) in mem-cache [%d]: %s!\n", + name->str, result->domain->name, ret, sss_strerror(ret)); + } + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + if (ret != EOK) { + sss_packet_set_size(packet, 0); + return ret; + } + + sss_packet_get_body(packet, &body, &body_len); + SAFEALIGN_COPY_UINT32(body, &num_results, NULL); + SAFEALIGN_SETMEM_UINT32(body + sizeof(uint32_t), 0, NULL); /* reserved */ + + return EOK; +} + +static bool is_group_filtered(struct sss_nc_ctx *ncache, + struct sss_domain_info *domain, + const char *grp_name, gid_t gid) +{ + int ret; + + if (grp_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Group with gid [%"SPRIgid"] has no name, this should never " + "happen, trying to continue without.\n", gid); + } else { + ret = sss_ncache_check_group(ncache, domain, grp_name); + if (ret == EEXIST) { + DEBUG(SSSDBG_TRACE_FUNC, "Group [%s] is filtered out! " + "(negative cache)", grp_name); + return true; + } + } + ret = sss_ncache_check_gid(ncache, domain, gid); + if (ret == EEXIST) { + DEBUG(SSSDBG_TRACE_FUNC, "Group [%"SPRIgid"] is filtered out! " + "(negative cache)", gid); + return true; + } + + return false; +} + +errno_t +sss_nss_protocol_fill_initgr(struct sss_nss_ctx *nss_ctx, + struct sss_nss_cmd_ctx *cmd_ctx, + struct sss_packet *packet, + struct cache_req_result *result) +{ + struct sss_domain_info *domain; + struct sss_domain_info *grp_dom; + struct ldb_message *user; + struct ldb_message *msg; + struct ldb_message *primary_group_msg; + const char *posix; + struct sized_string rawname; + struct sized_string canonical_name; + uint32_t num_results; + uint8_t *body; + size_t body_len; + size_t rp; + gid_t gid; + const char *grp_name; + gid_t orig_gid; + errno_t ret; + int i; + + if (result->count == 0) { + return ENOENT; + } + + domain = result->domain; + + /* num_results, reserved + gids */ + ret = sss_packet_grow(packet, (2 + result->count) * sizeof(uint32_t)); + if (ret != EOK) { + return ret; + } + sss_packet_get_body(packet, &body, &body_len); + rp = 2 * sizeof(uint32_t); + + user = result->msgs[0]; + gid = sss_view_ldb_msg_find_attr_as_uint64(domain, user, SYSDB_GIDNUM, 0); + orig_gid = sss_view_ldb_msg_find_attr_as_uint64(domain, user, + SYSDB_PRIMARY_GROUP_GIDNUM, + 0); + + /* Try to get the real gid in case the primary group's gid was overridden. */ + ret = sysdb_search_group_by_origgid(NULL, domain, orig_gid, NULL, + &primary_group_msg); + if (ret != EOK) { + if (ret == ENOENT) { + DEBUG(SSSDBG_FUNC_DATA, + "There is no override for group %" SPRIgid "\n", + orig_gid); + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Unable to find the original group id attribute for %" SPRIgid + ". Assuming there is none. [%d] %s\n", + orig_gid, ret, sss_strerror(ret)); + } + /* Just continue with what we have. */ + } else { + orig_gid = ldb_msg_find_attr_as_uint64(primary_group_msg, SYSDB_GIDNUM, + orig_gid); + talloc_free(primary_group_msg); + } + + /* If the GID of the original primary group is available but equal to the + * current primary GID it must not be added. */ + orig_gid = orig_gid == gid ? 0 : orig_gid; + + /* First message is user, skip it. */ + num_results = 0; + for (i = 1; i < result->count; i++) { + msg = result->msgs[i]; + grp_dom = find_domain_by_msg(domain, msg); + gid = sss_view_ldb_msg_find_attr_as_uint64(grp_dom, msg, SYSDB_GIDNUM, + 0); + posix = ldb_msg_find_attr_as_string(msg, SYSDB_POSIX, NULL); + grp_name = sss_view_ldb_msg_find_attr_as_string(grp_dom, msg, SYSDB_NAME, + NULL); + + if (gid == 0) { + if (posix != NULL && strcmp(posix, "FALSE") == 0) { + continue; + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Incomplete group object [%s] for initgroups! " + "Skipping.\n", ldb_dn_get_linearized(msg->dn)); + continue; + } + } + + if (is_group_filtered(nss_ctx->rctx->ncache, grp_dom, grp_name, gid)) { + continue; + } + + SAFEALIGN_COPY_UINT32(&body[rp], &gid, &rp); + num_results++; + + /* Do not add the GID of the original primary group if the user is + * already an explicit member of the group. */ + if (orig_gid == gid) { + orig_gid = 0; + } + } + + if (orig_gid == 0) { + /* Initialize allocated memory to be safe and make Valgrind happy. */ + SAFEALIGN_SET_UINT32(&body[rp], 0, &rp); + } else { + /* Insert original primary group into the result. */ + SAFEALIGN_COPY_UINT32(&body[rp], &orig_gid, &rp); + num_results++; + } + + if (nss_ctx->initgr_mc_ctx + && ((cmd_ctx->flags & SSS_NSS_EX_FLAG_INVALIDATE_CACHE) == 0) + && (nss_ctx->initgr_mc_ctx != NULL)) { + to_sized_string(&rawname, cmd_ctx->rawname); + to_sized_string(&canonical_name, sss_get_name_from_msg(domain, user)); + + ret = sss_mmap_cache_initgr_store(&nss_ctx->initgr_mc_ctx, &rawname, + &canonical_name, num_results, + body + 2 * sizeof(uint32_t)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to store initgroups %s (%s) in mem-cache [%d]: %s!\n", + rawname.str, domain->name, ret, sss_strerror(ret)); + sss_packet_set_size(packet, 0); + return ret; + } + } + + sss_packet_get_body(packet, &body, &body_len); + SAFEALIGN_COPY_UINT32(body, &num_results, NULL); + SAFEALIGN_SETMEM_UINT32(body + sizeof(uint32_t), 0, NULL); /* reserved */ + + return EOK; +} diff --git a/src/responder/nss/nss_protocol_hostent.c b/src/responder/nss/nss_protocol_hostent.c new file mode 100644 index 0000000..687baad --- /dev/null +++ b/src/responder/nss/nss_protocol_hostent.c @@ -0,0 +1,299 @@ +/* + SSSD + + Authors: + Samuel Cabrero <scabrero@suse.com> + + Copyright (C) 2019 SUSE LINUX GmbH, Nuernberg, Germany. + + 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 "db/sysdb.h" +#include "db/sysdb_iphosts.h" +#include "responder/nss/nss_protocol.h" + +static errno_t +sss_nss_get_hostent(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + struct ldb_message *msg, + struct sized_string *_name) +{ + TALLOC_CTX *tmp_ctx; + const char *name; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + /* Get name */ + name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); + if (name == NULL) { + ret = ERR_INTERNAL; + goto done; + } + + name = sss_get_cased_name(tmp_ctx, name, domain->case_preserve); + if (name == NULL) { + ret = ENOMEM; + goto done; + } + + /* Set output variables */ + + talloc_steal(mem_ctx, name); + + to_sized_string(_name, name); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +sss_nss_get_host_aliases(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + struct ldb_message *msg, + const char *name, + struct sized_string **_aliases, + uint32_t *_num_aliases) +{ + struct ldb_message_element *el; + struct sized_string *aliases = NULL; + uint32_t num_aliases; + const char *alias; + errno_t ret; + int i; + + el = ldb_msg_find_element(msg, SYSDB_NAME_ALIAS); + if (el == NULL) { + *_num_aliases = 0; + *_aliases = NULL; + ret = EOK; + goto done; + } + + aliases = talloc_zero_array(mem_ctx, struct sized_string, + el->num_values + 1); + if (aliases == NULL) { + ret = ENOMEM; + goto done; + } + + num_aliases = 0; + for (i = 0; i < el->num_values; i++) { + alias = (const char *)el->values[i].data; + + if (sss_string_equal(domain->case_sensitive, alias, name)) { + continue; + } + + /* Element value remains in the message, we don't need to strdup it. */ + to_sized_string(&aliases[num_aliases], alias); + num_aliases++; + } + + *_aliases = aliases; + *_num_aliases = num_aliases; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(aliases); + } + + return ret; +} + +static errno_t +sss_nss_get_host_addresses(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + struct ldb_message *msg, + const char *name, + struct sized_string **_addresses, + uint32_t *_num_addresses) +{ + struct ldb_message_element *el; + struct sized_string *addresses = NULL; + uint32_t num_addresses; + const char *addr; + errno_t ret; + int i; + + el = ldb_msg_find_element(msg, SYSDB_IP_HOST_ATTR_ADDRESS); + if (el == NULL) { + *_num_addresses = 0; + *_addresses = NULL; + ret = EOK; + goto done; + } + + addresses = talloc_zero_array(mem_ctx, struct sized_string, + el->num_values + 1); + if (addresses == NULL) { + ret = ENOMEM; + goto done; + } + + num_addresses = 0; + for (i = 0; i < el->num_values; i++) { + addr = (const char *)el->values[i].data; + + /* Element value remains in the message, we don't need to strdup it. */ + to_sized_string(&addresses[num_addresses], addr); + num_addresses++; + } + + *_addresses = addresses; + *_num_addresses = num_addresses; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(addresses); + } + + return ret; +} + +errno_t +sss_nss_protocol_fill_hostent(struct sss_nss_ctx *nss_ctx, + struct sss_nss_cmd_ctx *cmd_ctx, + struct sss_packet *packet, + struct cache_req_result *result) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_message *msg; + struct sized_string name; + struct sized_string *aliases; + struct sized_string *addresses; + uint32_t num_aliases; + uint32_t num_addresses; + uint32_t num_results; + size_t rp; + size_t body_len; + uint8_t *body; + int i; + int j; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + /* First two fields (length and reserved), filled up later. */ + ret = sss_packet_grow(packet, 2 * sizeof(uint32_t)); + if (ret != EOK) { + return ret; + } + + rp = 2 * sizeof(uint32_t); + + num_results = 0; + for (i = 0; i < result->count; i++) { + talloc_free_children(tmp_ctx); + msg = result->msgs[i]; + + ret = sss_nss_get_hostent(tmp_ctx, result->domain, msg, &name); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Unable to get host information, skipping... [%d]: %s\n", + ret, sss_strerror(ret)); + continue; + } + + ret = sss_nss_get_host_aliases(tmp_ctx, result->domain, msg, name.str, + &aliases, &num_aliases); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Unable to get host aliases, skipping... [%d]: %s\n", + ret, sss_strerror(ret)); + continue; + } + + ret = sss_nss_get_host_addresses(tmp_ctx, result->domain, msg, name.str, + &addresses, &num_addresses); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Unable to get host addresses, skipping... [%d]: %s\n", + ret, sss_strerror(ret)); + continue; + } + + /* Adjust packet size */ + + ret = sss_packet_grow(packet, 2 * sizeof(uint32_t) + name.len); + if (ret != EOK) { + goto done; + } + + sss_packet_get_body(packet, &body, &body_len); + + /* Fill packet */ + + SAFEALIGN_SET_UINT32(&body[rp], num_aliases, &rp); + SAFEALIGN_SET_UINT32(&body[rp], num_addresses, &rp); + SAFEALIGN_SET_STRING(&body[rp], name.str, name.len, &rp); + + /* Store aliases */ + for (j = 0; j < num_aliases; j++) { + ret = sss_packet_grow(packet, aliases[j].len); + if (ret != EOK) { + goto done; + } + sss_packet_get_body(packet, &body, &body_len); + + SAFEALIGN_SET_STRING(&body[rp], aliases[j].str, aliases[j].len, + &rp); + } + + /* Store addresses */ + for (j = 0; j < num_addresses; j++) { + ret = sss_packet_grow(packet, addresses[j].len); + if (ret != EOK) { + goto done; + } + sss_packet_get_body(packet, &body, &body_len); + + SAFEALIGN_SET_STRING(&body[rp], addresses[j].str, addresses[j].len, + &rp); + } + + num_results++; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + if (ret != EOK) { + sss_packet_set_size(packet, 0); + return ret; + } + + sss_packet_get_body(packet, &body, &body_len); + SAFEALIGN_COPY_UINT32(body, &num_results, NULL); + SAFEALIGN_SETMEM_UINT32(body + sizeof(uint32_t), 0, NULL); /* reserved */ + + return EOK; +} diff --git a/src/responder/nss/nss_protocol_netent.c b/src/responder/nss/nss_protocol_netent.c new file mode 100644 index 0000000..0517726 --- /dev/null +++ b/src/responder/nss/nss_protocol_netent.c @@ -0,0 +1,243 @@ +/* + SSSD + + Authors: + Samuel Cabrero <scabrero@suse.com> + + Copyright (C) 2020 SUSE LINUX GmbH, Nuernberg, Germany. + + 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 "db/sysdb.h" +#include "db/sysdb_ipnetworks.h" +#include "responder/nss/nss_protocol.h" + +static errno_t +sss_nss_get_netent(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + struct ldb_message *msg, + struct sized_string *_name, + struct sized_string *_addr) +{ + TALLOC_CTX *tmp_ctx; + const char *name; + const char *addr; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + /* Get name */ + name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); + if (name == NULL) { + ret = ERR_INTERNAL; + goto done; + } + + name = sss_get_cased_name(tmp_ctx, name, domain->case_preserve); + if (name == NULL) { + ret = ENOMEM; + goto done; + } + + /* Get address */ + addr = ldb_msg_find_attr_as_string(msg, SYSDB_IP_NETWORK_ATTR_NUMBER, + NULL); + if (addr == NULL) { + ret = ERR_INTERNAL; + goto done; + } + + addr = sss_get_cased_name(tmp_ctx, addr, domain->case_preserve); + if (addr == NULL) { + ret = ENOMEM; + goto done; + } + + /* Set output variables */ + + talloc_steal(mem_ctx, name); + talloc_steal(mem_ctx, addr); + + to_sized_string(_name, name); + to_sized_string(_addr, addr); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +sss_nss_get_network_aliases(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + struct ldb_message *msg, + const char *name, + struct sized_string **_aliases, + uint32_t *_num_aliases) +{ + struct ldb_message_element *el; + struct sized_string *aliases = NULL; + uint32_t num_aliases; + const char *alias; + errno_t ret; + int i; + + el = ldb_msg_find_element(msg, SYSDB_NAME_ALIAS); + if (el == NULL) { + *_num_aliases = 0; + *_aliases = NULL; + ret = EOK; + goto done; + } + + aliases = talloc_zero_array(mem_ctx, struct sized_string, + el->num_values + 1); + if (aliases == NULL) { + ret = ENOMEM; + goto done; + } + + num_aliases = 0; + for (i = 0; i < el->num_values; i++) { + alias = (const char *)el->values[i].data; + + if (sss_string_equal(domain->case_sensitive, alias, name)) { + continue; + } + + /* Element value remains in the message, we don't need to strdup it. */ + to_sized_string(&aliases[num_aliases], alias); + num_aliases++; + } + + *_aliases = aliases; + *_num_aliases = num_aliases; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(aliases); + } + + return ret; +} + +errno_t +sss_nss_protocol_fill_netent(struct sss_nss_ctx *nss_ctx, + struct sss_nss_cmd_ctx *cmd_ctx, + struct sss_packet *packet, + struct cache_req_result *result) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_message *msg; + struct sized_string name; + struct sized_string addr; + struct sized_string *aliases; + uint32_t num_aliases; + uint32_t num_results; + size_t rp; + size_t body_len; + uint8_t *body; + int i; + int j; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + /* First two fields (length and reserved), filled up later. */ + ret = sss_packet_grow(packet, 2 * sizeof(uint32_t)); + if (ret != EOK) { + return ret; + } + + rp = 2 * sizeof(uint32_t); + + num_results = 0; + for (i = 0; i < result->count; i++) { + talloc_free_children(tmp_ctx); + msg = result->msgs[i]; + + ret = sss_nss_get_netent(tmp_ctx, result->domain, msg, &name, &addr); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Unable to get network information, skipping... [%d]: %s\n", + ret, sss_strerror(ret)); + continue; + } + + ret = sss_nss_get_network_aliases(tmp_ctx, result->domain, msg, name.str, + &aliases, &num_aliases); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Unable to get network aliases, skipping... [%d]: %s\n", + ret, sss_strerror(ret)); + continue; + } + + /* Adjust packet size */ + + ret = sss_packet_grow(packet, sizeof(uint32_t) + name.len + addr.len); + if (ret != EOK) { + goto done; + } + + sss_packet_get_body(packet, &body, &body_len); + + /* Fill packet */ + + SAFEALIGN_SET_UINT32(&body[rp], num_aliases, &rp); + SAFEALIGN_SET_STRING(&body[rp], name.str, name.len, &rp); + SAFEALIGN_SET_STRING(&body[rp], addr.str, addr.len, &rp); + + /* Store aliases */ + for (j = 0; j < num_aliases; j++) { + ret = sss_packet_grow(packet, aliases[j].len); + if (ret != EOK) { + goto done; + } + sss_packet_get_body(packet, &body, &body_len); + + SAFEALIGN_SET_STRING(&body[rp], aliases[j].str, aliases[j].len, + &rp); + } + + num_results++; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + if (ret != EOK) { + sss_packet_set_size(packet, 0); + return ret; + } + + sss_packet_get_body(packet, &body, &body_len); + SAFEALIGN_COPY_UINT32(body, &num_results, NULL); + SAFEALIGN_SETMEM_UINT32(body + sizeof(uint32_t), 0, NULL); /* reserved */ + + return EOK; +} diff --git a/src/responder/nss/nss_protocol_netgr.c b/src/responder/nss/nss_protocol_netgr.c new file mode 100644 index 0000000..be3380b --- /dev/null +++ b/src/responder/nss/nss_protocol_netgr.c @@ -0,0 +1,181 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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 "db/sysdb.h" +#include "db/sysdb_services.h" +#include "responder/nss/nss_protocol.h" + +static errno_t +sss_nss_protocol_fill_netgr_triple(struct sss_packet *packet, + struct sysdb_netgroup_ctx *entry, + size_t *_rp) +{ + struct sized_string host; + struct sized_string user; + struct sized_string domain; + size_t body_len; + uint8_t *body; + errno_t ret; + + to_sized_string(&host, entry->value.triple.hostname); + to_sized_string(&user, entry->value.triple.username); + to_sized_string(&domain, entry->value.triple.domainname); + + if (host.len == 0) { + host.len = 1; + host.str = ""; + } + + if (user.len == 0) { + user.len = 1; + user.str = ""; + } + + if (domain.len == 0) { + domain.len = 1; + domain.str = ""; + } + + ret = sss_packet_grow(packet, sizeof(uint32_t) + + host.len + user.len + domain.len); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to grow packet!\n"); + return ret; + } + + sss_packet_get_body(packet, &body, &body_len); + + SAFEALIGN_SET_UINT32(&body[*_rp], SSS_NETGR_REP_TRIPLE, _rp); + SAFEALIGN_SET_STRING(&body[*_rp], host.str, host.len, _rp); + SAFEALIGN_SET_STRING(&body[*_rp], user.str, user.len, _rp); + SAFEALIGN_SET_STRING(&body[*_rp], domain.str, domain.len, _rp); + + return EOK; +} + +static errno_t +sss_nss_protocol_fill_netgr_member(struct sss_packet *packet, + struct sysdb_netgroup_ctx *entry, + size_t *_rp) +{ + struct sized_string group; + size_t body_len; + uint8_t *body; + errno_t ret; + + if (entry->value.groupname == NULL || entry->value.groupname[0] == '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, "Empty netgroup member!\n"); + return EINVAL; + } + + to_sized_string(&group, entry->value.groupname); + + ret = sss_packet_grow(packet, sizeof(uint32_t) + group.len); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to grow packet!\n"); + return ret; + } + + sss_packet_get_body(packet, &body, &body_len); + + SAFEALIGN_SET_UINT32(&body[*_rp], SSS_NETGR_REP_GROUP, _rp); + SAFEALIGN_SET_STRING(&body[*_rp], group.str, group.len, _rp); + + return EOK; +} + +errno_t +sss_nss_protocol_fill_netgrent(struct sss_nss_ctx *nss_ctx, + struct sss_nss_cmd_ctx *cmd_ctx, + struct sss_packet *packet, + struct cache_req_result *result) +{ + struct sysdb_netgroup_ctx **entries; + struct sysdb_netgroup_ctx *entry; + struct sss_nss_enum_index *idx; + uint32_t num_results; + size_t rp; + size_t body_len; + uint8_t *body; + errno_t ret; + + idx = cmd_ctx->enum_index; + entries = cmd_ctx->enum_ctx->netgroup; + + if (idx->result > cmd_ctx->enum_ctx->netgroup_count) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Inconsistent state while processing netgroups.\n"); + ret = EINVAL; + goto done; + } + + /* First two fields (length and reserved), filled up later. */ + ret = sss_packet_grow(packet, 2 * sizeof(uint32_t)); + if (ret != EOK) { + return ret; + } + + rp = 2 * sizeof(uint32_t); + + if (entries == NULL) { + num_results = 0; + ret = EOK; + goto done; + } + + num_results = 1; /* group was found */ + for (; entries[idx->result] != NULL; idx->result++) { + + entry = entries[idx->result]; + + switch (entry->type) { + case SYSDB_NETGROUP_TRIPLE_VAL: + ret = sss_nss_protocol_fill_netgr_triple(packet, entry, &rp); + break; + case SYSDB_NETGROUP_GROUP_VAL: + ret = sss_nss_protocol_fill_netgr_member(packet, entry, &rp); + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected value type %d!\n", entry->type); + ret = ERR_INTERNAL; + break; + } + + if (ret != EOK) { + goto done; + } + + num_results++; + } + + ret = EOK; + +done: + if (ret != EOK) { + sss_packet_set_size(packet, 0); + return ret; + } + + sss_packet_get_body(packet, &body, &body_len); + SAFEALIGN_COPY_UINT32(body, &num_results, NULL); + SAFEALIGN_SETMEM_UINT32(body + sizeof(uint32_t), 0, NULL); /* reserved */ + + return EOK; +} diff --git a/src/responder/nss/nss_protocol_pwent.c b/src/responder/nss/nss_protocol_pwent.c new file mode 100644 index 0000000..3e1f31f --- /dev/null +++ b/src/responder/nss/nss_protocol_pwent.c @@ -0,0 +1,338 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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 "responder/nss/nss_protocol.h" +#include "util/sss_nss.h" + +static uint32_t +sss_nss_get_gid(struct sss_domain_info *domain, + struct ldb_message *msg) +{ + uint32_t gid; + + /* First, try to return overridden gid. */ + if (DOM_HAS_VIEWS(domain)) { + gid = ldb_msg_find_attr_as_uint64(msg, OVERRIDE_PREFIX SYSDB_GIDNUM, + 0); + if (gid != 0) { + return gid; + } + } + + /* Try to return domain gid override. */ + if (domain->override_gid != 0) { + return domain->override_gid; + } + + /* Return original gid. */ + return ldb_msg_find_attr_as_uint64(msg, SYSDB_GIDNUM, 0); +} + +static const char * +sss_nss_get_homedir_override(TALLOC_CTX *mem_ctx, + struct ldb_message *msg, + struct sss_nss_ctx *nctx, + struct sss_domain_info *dom, + struct sss_nss_homedir_ctx *homedir_ctx) +{ + const char *homedir; + bool is_override = false; + + homedir = sss_view_ldb_msg_find_attr_as_string_ex(dom, msg, SYSDB_HOMEDIR, + NULL, &is_override); + homedir_ctx->original = homedir; + + /* Check to see which homedir_prefix to use. */ + if (dom->homedir_substr != NULL) { + homedir_ctx->config_homedir_substr = dom->homedir_substr; + } else if (nctx->homedir_substr != NULL) { + homedir_ctx->config_homedir_substr = nctx->homedir_substr; + } + + /* Individual overrides have the highest priority, only templates will be + * expanded and no further options will be evaluated. */ + if (is_override) { + return expand_homedir_template(mem_ctx, homedir, + dom->case_preserve, homedir_ctx); + } + + /* Here we skip the files provider as it should always return *only* + * what's in the files and nothing else. + */ + if (!is_files_provider(dom)) { + /* Check whether we are unconditionally overriding the server + * for home directory locations. + */ + if (dom->override_homedir) { + return expand_homedir_template(mem_ctx, dom->override_homedir, + dom->case_preserve, homedir_ctx); + } else if (nctx->override_homedir) { + return expand_homedir_template(mem_ctx, nctx->override_homedir, + dom->case_preserve, homedir_ctx); + } + } + + if (!homedir || *homedir == '\0') { + /* In the case of a NULL or empty homedir, check to see if + * we have a fallback homedir to use. + */ + if (dom->fallback_homedir) { + return expand_homedir_template(mem_ctx, dom->fallback_homedir, + dom->case_preserve, homedir_ctx); + } else if (nctx->fallback_homedir) { + return expand_homedir_template(mem_ctx, nctx->fallback_homedir, + dom->case_preserve, homedir_ctx); + } + } + + /* Provider can also return template, try to expand it.*/ + return expand_homedir_template(mem_ctx, homedir, + dom->case_preserve, homedir_ctx); +} + +static const char * +sss_nss_get_homedir(TALLOC_CTX *mem_ctx, + struct sss_nss_ctx *nss_ctx, + struct sss_domain_info *domain, + struct ldb_message *msg, + const char *orig_name, + const char *upn, + uid_t uid) +{ + struct sss_nss_homedir_ctx hd_ctx = { 0 }; + const char *homedir; + + hd_ctx.username = orig_name; + hd_ctx.uid = uid; + hd_ctx.domain = domain->name; + hd_ctx.upn = upn; + + homedir = sss_nss_get_homedir_override(mem_ctx, msg, nss_ctx, domain, &hd_ctx); + if (homedir == NULL) { + return ""; + } + + return homedir; +} + +static errno_t +sss_nss_get_shell(struct sss_nss_ctx *nss_ctx, + struct sss_domain_info *domain, + struct ldb_message *msg, + const char *name, + uint32_t uid, + const char **_shell) +{ + const char *shell = NULL; + + if (nss_ctx->rctx->sr_conf.scope != SESSION_RECORDING_SCOPE_NONE) { + const char *sr_enabled; + sr_enabled = ldb_msg_find_attr_as_string( + msg, SYSDB_SESSION_RECORDING, NULL); + if (sr_enabled == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "%s attribute not found for %s[%u]! Skipping\n", + SYSDB_SESSION_RECORDING, name, uid); + return EINVAL; + } else if (strcmp(sr_enabled, "TRUE") == 0) { + shell = SESSION_RECORDING_SHELL; + } else if (strcmp(sr_enabled, "FALSE") != 0) { + DEBUG(SSSDBG_OP_FAILURE, + "Skipping %s[%u] " + "because its %s attribute value is invalid: %s\n", + name, uid, SYSDB_SESSION_RECORDING, sr_enabled); + return EINVAL; + } + } + if (shell == NULL) { + shell = sss_resp_get_shell_override(msg, nss_ctx->rctx, domain); + } + + *_shell = shell; + return EOK; +} + +static errno_t +sss_nss_get_pwent(TALLOC_CTX *mem_ctx, + struct sss_nss_ctx *nss_ctx, + struct sss_domain_info *domain, + struct ldb_message *msg, + uint32_t *_uid, + uint32_t *_gid, + struct sized_string **_name, + struct sized_string *_gecos, + struct sized_string *_homedir, + struct sized_string *_shell) +{ + const char *upn; + const char *name; + const char *gecos; + const char *homedir; + const char *shell; + uint32_t gid; + uint32_t uid; + errno_t ret; + + /* Get fields. */ + upn = ldb_msg_find_attr_as_string(msg, SYSDB_UPN, NULL); + name = sss_get_name_from_msg(domain, msg); + gid = sss_nss_get_gid(domain, msg); + uid = sss_view_ldb_msg_find_attr_as_uint64(domain, msg, SYSDB_UIDNUM, 0); + + if (name == NULL || uid == 0 || gid == 0) { + DEBUG(SSSDBG_OP_FAILURE, + "Incomplete user object for %s[%u]! Skipping\n", + name ? name : "<NULL>", uid); + return EINVAL; + } + + gecos = sss_view_ldb_msg_find_attr_as_string(domain, msg, SYSDB_GECOS, + NULL); + homedir = sss_nss_get_homedir(mem_ctx, nss_ctx, domain, msg, name, upn, uid); + ret = sss_nss_get_shell(nss_ctx, domain, msg, name, uid, &shell); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "failed retrieving shell for %s[%u], skipping [%d]: %s\n", + name, uid, ret, sss_strerror(ret)); + return ret; + } + + /* Convert to sized strings. */ + ret = sized_output_name(mem_ctx, nss_ctx->rctx, name, domain, _name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sized_output_name failed, skipping [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + to_sized_string(_gecos, gecos == NULL ? "" : gecos); + to_sized_string(_shell, shell); + to_sized_string(_homedir, homedir); + + *_gid = gid; + *_uid = uid; + + return EOK; +} + +errno_t +sss_nss_protocol_fill_pwent(struct sss_nss_ctx *nss_ctx, + struct sss_nss_cmd_ctx *cmd_ctx, + struct sss_packet *packet, + struct cache_req_result *result) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_message *msg; + struct sized_string pwfield; + struct sized_string *name; + struct sized_string gecos; + struct sized_string homedir; + struct sized_string shell; + uint32_t gid; + uint32_t uid; + uint32_t num_results; + size_t rp; + size_t body_len; + uint8_t *body; + int i; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + /* First two fields (length and reserved), filled up later. */ + ret = sss_packet_grow(packet, 2 * sizeof(uint32_t)); + if (ret != EOK) { + return ret; + } + + rp = 2 * sizeof(uint32_t); + + num_results = 0; + for (i = 0; i < result->count; i++) { + talloc_free_children(tmp_ctx); + msg = result->msgs[i]; + + /* Password field content. */ + to_sized_string(&pwfield, sss_nss_get_pwfield(nss_ctx, result->domain)); + + ret = sss_nss_get_pwent(tmp_ctx, nss_ctx, result->domain, msg, &uid, &gid, + &name, &gecos, &homedir, &shell); + if (ret != EOK) { + continue; + } + + /* Adjust packet size: uid, gid + string fields. */ + + ret = sss_packet_grow(packet, 2 * sizeof(uint32_t) + + name->len + gecos.len + homedir.len + + shell.len + pwfield.len); + if (ret != EOK) { + goto done; + } + + sss_packet_get_body(packet, &body, &body_len); + + /* Fill packet. */ + + SAFEALIGN_SET_UINT32(&body[rp], uid, &rp); + SAFEALIGN_SET_UINT32(&body[rp], gid, &rp); + SAFEALIGN_SET_STRING(&body[rp], name->str, name->len, &rp); + SAFEALIGN_SET_STRING(&body[rp], pwfield.str, pwfield.len, &rp); + SAFEALIGN_SET_STRING(&body[rp], gecos.str, gecos.len, &rp); + SAFEALIGN_SET_STRING(&body[rp], homedir.str, homedir.len, &rp); + SAFEALIGN_SET_STRING(&body[rp], shell.str, shell.len, &rp); + + num_results++; + + /* Do not store entry in memory cache during enumeration or when + * requested or if cache explicitly disabled. */ + if (!cmd_ctx->enumeration + && ((cmd_ctx->flags & SSS_NSS_EX_FLAG_INVALIDATE_CACHE) == 0) + && (nss_ctx->pwd_mc_ctx != NULL)) { + ret = sss_mmap_cache_pw_store(&nss_ctx->pwd_mc_ctx, name, &pwfield, + uid, gid, &gecos, &homedir, &shell); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to store user %s (%s) in mmap cache [%d]: %s!\n", + name->str, result->domain->name, ret, sss_strerror(ret)); + } + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + if (ret != EOK) { + sss_packet_set_size(packet, 0); + return ret; + } + + sss_packet_get_body(packet, &body, &body_len); + SAFEALIGN_COPY_UINT32(body, &num_results, NULL); + SAFEALIGN_SETMEM_UINT32(body + sizeof(uint32_t), 0, NULL); /* reserved */ + + return EOK; +} diff --git a/src/responder/nss/nss_protocol_sid.c b/src/responder/nss/nss_protocol_sid.c new file mode 100644 index 0000000..69d61bb --- /dev/null +++ b/src/responder/nss/nss_protocol_sid.c @@ -0,0 +1,704 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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 "util/crypto/sss_crypto.h" +#include "responder/nss/nss_protocol.h" + +static errno_t +find_sss_id_type(struct ldb_message *msg, + bool mpg, + enum sss_id_type *id_type) +{ + size_t c; + struct ldb_message_element *el; + struct ldb_val *val = NULL; + + el = ldb_msg_find_element(msg, SYSDB_OBJECTCATEGORY); + if (el == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Objectcategory attribute not found.\n"); + return EINVAL; + } + + for (c = 0; c < el->num_values; c++) { + val = &(el->values[c]); + if (strncasecmp(SYSDB_USER_CLASS, + (char *)val->data, val->length) == 0) { + break; + } + } + + if (c == el->num_values) { + *id_type = SSS_ID_TYPE_GID; + } else { + if (mpg) { + *id_type = SSS_ID_TYPE_BOTH; + } else { + *id_type = SSS_ID_TYPE_UID; + } + } + + return EOK; +} + +static errno_t +sss_nss_get_id_type(struct sss_nss_cmd_ctx *cmd_ctx, + struct cache_req_result *result, + enum sss_id_type *_type) +{ + errno_t ret; + bool mpg; + + /* Well known objects are always groups. */ + if (result->well_known_object) { + *_type = SSS_ID_TYPE_GID; + return EOK; + } + + mpg = sss_domain_is_mpg(result->domain) || sss_domain_is_hybrid(result->domain); + ret = find_sss_id_type(result->msgs[0], mpg, _type); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Unable to find ID type [%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + + return EOK; +} + +static errno_t +sss_nss_get_sid_id_type(struct sss_nss_cmd_ctx *cmd_ctx, + struct cache_req_result *result, + const char **_sid, + uint64_t *_id, + enum sss_id_type *_type) +{ + errno_t ret; + size_t c; + const char *tmp; + const char *user_sid = NULL; + const char *group_sid = NULL; + uint64_t user_uid = 0; + uint64_t user_gid = 0; + uint64_t group_gid = 0; + enum sss_id_type ltype; + + if (result->count == 1) { + *_sid = ldb_msg_find_attr_as_string(result->msgs[0], + SYSDB_SID_STR, NULL); + if (*_sid == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing SID.\n"); + return EINVAL; + } + ret = sss_nss_get_id_type(cmd_ctx, result, _type); + if (ret == EOK ) { + if (*_type == SSS_ID_TYPE_GID) { + *_id = ldb_msg_find_attr_as_uint64(result->msgs[0], + SYSDB_GIDNUM, 0); + } else { + *_id = ldb_msg_find_attr_as_uint64(result->msgs[0], + SYSDB_UIDNUM, 0); + } + } + return ret; + } + + for (c = 0; c < result->count; c++) { + ret = find_sss_id_type(result->msgs[c], + false /* we are only interested in the type + * of the object, so mpg setting can + * be ignored */, + <ype); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Unable to find ID type, ignored [%d][%s].\n", + ret, sss_strerror(ret)); + continue; + } + + tmp = ldb_msg_find_attr_as_string(result->msgs[c], + SYSDB_SID_STR, NULL); + if (tmp == NULL) { + continue; + } + + if (ltype == SSS_ID_TYPE_GID) { + if (tmp != NULL && group_sid != NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Search for SID found multiple groups with SIDs: %s, %s;" + " request failed.\n", tmp, group_sid); + return EINVAL; + } + group_sid = tmp; + group_gid = ldb_msg_find_attr_as_uint64(result->msgs[c], + SYSDB_GIDNUM, 0); + } else { + if (tmp != NULL && user_sid != NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Search for SID found multiple users with SIDs: %s, %s; " + "request failed.\n", tmp, user_sid); + return EINVAL; + } + user_sid = tmp; + user_uid = ldb_msg_find_attr_as_uint64(result->msgs[c], + SYSDB_UIDNUM, 0); + user_gid = ldb_msg_find_attr_as_uint64(result->msgs[c], + SYSDB_GIDNUM, 0); + } + } + + if (user_sid == NULL && group_sid == NULL) { + /* No SID in the results */ + return ENOENT; + } else if (user_sid != NULL && group_sid == NULL) { + /* There is only one user with a SID in the results */ + *_sid = user_sid; + *_id = user_uid; + *_type = SSS_ID_TYPE_UID; + } else if (user_sid == NULL && group_sid != NULL) { + /* There is only one group with a SID in the results */ + *_sid = group_sid; + *_id = group_gid; + *_type = SSS_ID_TYPE_GID; + } else if (user_sid != NULL && group_sid != NULL && user_uid != 0 + && user_uid == user_gid && user_gid == group_gid) { + /* Manually created user-private-group */ + *_sid = user_sid; + *_id = user_uid; + *_type = SSS_ID_TYPE_UID; + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Found user with SID [%s] and group with SID [%s] during a " + "single request, cannot handle this case.\n", + user_sid, group_sid); + /* Unrelated user and group both with SIDs are returned, we cannot + * handle this case. */ + return EINVAL; + } + + return EOK; +} + +errno_t +sss_nss_protocol_fill_sid(struct sss_nss_ctx *nss_ctx, + struct sss_nss_cmd_ctx *cmd_ctx, + struct sss_packet *packet, + struct cache_req_result *result) +{ + struct sized_string sz_sid; + enum sss_id_type id_type; + const char *sid; + uint64_t id; + size_t rp = 0; + size_t body_len; + uint8_t *body; + errno_t ret; + + ret = sss_nss_get_sid_id_type(cmd_ctx, result, &sid, &id, &id_type); + if (ret != EOK) { + return ret; + } + + to_sized_string(&sz_sid, sid); + + ret = sss_packet_grow(packet, sz_sid.len + 3 * sizeof(uint32_t)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_packet_grow failed.\n"); + return ret; + } + + sss_packet_get_body(packet, &body, &body_len); + + SAFEALIGN_SET_UINT32(&body[rp], 1, &rp); /* Num results. */ + SAFEALIGN_SET_UINT32(&body[rp], 0, &rp); /* Reserved. */ + SAFEALIGN_SET_UINT32(&body[rp], id_type, &rp); + SAFEALIGN_SET_STRING(&body[rp], sz_sid.str, sz_sid.len, &rp); + + if (nss_ctx->sid_mc_ctx != NULL) { + /* no need to check for SSS_NSS_EX_FLAG_INVALIDATE_CACHE since + * SID related requests don't support 'flags' + */ + if (id == 0 || id >= UINT32_MAX) { + DEBUG(SSSDBG_OP_FAILURE, "Invalid POSIX ID %lu\n", id); + return EOK; + } + ret = sss_mmap_cache_sid_store(&nss_ctx->sid_mc_ctx, &sz_sid, + (uint32_t)id, id_type, + cmd_ctx->type != CACHE_REQ_OBJECT_BY_ID); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to store SID='%s' / ID=%lu in mmap cache [%d]: %s!\n", + sz_sid.str, id, ret, sss_strerror(ret)); + } + } + + return EOK; +} + +static errno_t process_attr_list(TALLOC_CTX *mem_ctx, struct ldb_message *msg, + const char **attr_list, + struct sized_string **_keys, + struct sized_string **_vals, + size_t *array_size, size_t *sum, + size_t *found) +{ + size_t c; + size_t d; + struct sized_string *keys; + struct sized_string *vals; + struct ldb_val val; + struct ldb_message_element *el; + bool use_base64; + + keys = *_keys; + vals = *_vals; + + for (c = 0; attr_list[c] != NULL; c++) { + el = ldb_msg_find_element(msg, attr_list[c]); + if (el != NULL && el->num_values > 0) { + if (el->num_values > 1) { + *array_size += el->num_values; + keys = talloc_realloc(mem_ctx, keys, struct sized_string, + *array_size); + vals = talloc_realloc(mem_ctx, vals, struct sized_string, + *array_size); + if (keys == NULL || vals == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_array failed.\n"); + return ENOMEM; + } + } + + use_base64 = false; + if (strcmp(attr_list[c], SYSDB_USER_CERT) == 0) { + use_base64 = true; + } + + for (d = 0; d < el->num_values; d++) { + to_sized_string(&keys[*found], attr_list[c]); + *sum += keys[*found].len; + if (use_base64) { + val.data = (uint8_t *)sss_base64_encode(vals, + el->values[d].data, + el->values[d].length); + if (val.data != NULL) { + val.length = strlen((char *)val.data); + } + } else { + val = el->values[d]; + } + + if (val.data == NULL || val.data[val.length] != '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unexpected attribute value found for [%s].\n", + attr_list[c]); + return EINVAL; + } + to_sized_string(&vals[*found], (const char *)val.data); + *sum += vals[*found].len; + + (*found)++; + } + } + } + + *_keys = keys; + *_vals = vals; + + return EOK; +} + +errno_t +sss_nss_protocol_fill_orig(struct sss_nss_ctx *nss_ctx, + struct sss_nss_cmd_ctx *cmd_ctx, + struct sss_packet *packet, + struct cache_req_result *result) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_message *msg = result->msgs[0]; + const char **full_attrs = NULL; + enum sss_id_type id_type; + struct sized_string *keys; + struct sized_string *vals; + size_t full_attrs_count = 0; + size_t array_size; + size_t sum; + size_t found; + size_t i; + size_t rp = 0; + size_t body_len; + uint8_t *body; + errno_t ret; + + if (result->count != 1) { + DEBUG(SSSDBG_OP_FAILURE, + "Unexpected number of results [%u], expected [1].\n", + result->count); + return EINVAL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sss_nss_get_id_type(cmd_ctx, result, &id_type); + if (ret != EOK) { + return ret; + } + + if (nss_ctx->full_attribute_list != NULL) { + full_attrs = nss_ctx->full_attribute_list; + for (full_attrs_count = 0; + full_attrs[full_attrs_count] != NULL; + full_attrs_count++); + } + + array_size = full_attrs_count; + keys = talloc_array(tmp_ctx, struct sized_string, array_size); + vals = talloc_array(tmp_ctx, struct sized_string, array_size); + if (keys == NULL || vals == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_array failed.\n"); + ret = ENOMEM; + goto done; + } + + sum = 0; + found = 0; + + if (full_attrs_count != 0) { + ret = process_attr_list(tmp_ctx, msg, full_attrs, &keys, &vals, + &array_size, &sum, &found); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "process_attr_list failed.\n"); + goto done; + } + } + + ret = sss_packet_grow(packet, sum + 3 * sizeof(uint32_t)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_packet_grow failed.\n"); + goto done; + } + + sss_packet_get_body(packet, &body, &body_len); + SAFEALIGN_SETMEM_UINT32(&body[rp], 1, &rp); /* Num results */ + SAFEALIGN_SETMEM_UINT32(&body[rp], 0, &rp); /* reserved */ + SAFEALIGN_COPY_UINT32(&body[rp], &id_type, &rp); + for (i = 0; i < found; i++) { + SAFEALIGN_SET_STRING(&body[rp], keys[i].str, keys[i].len, &rp); + SAFEALIGN_SET_STRING(&body[rp], vals[i].str, vals[i].len, &rp); + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t +sss_nss_get_well_known_name(TALLOC_CTX *mem_ctx, + struct resp_ctx *rctx, + struct cache_req_result *result, + struct sized_string **_sz_name) +{ + struct sized_string *sz_name; + const char *fq_name = NULL; + const char *domname; + const char *name; + + name = result->lookup_name; + if (name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing name.\n"); + return EINVAL; + } + + sz_name = talloc_zero(mem_ctx, struct sized_string); + if (sz_name == NULL) { + return ENOMEM; + } + + domname = result->domain != NULL + ? result->domain->name + : result->well_known_domain; + + if (domname != NULL) { + fq_name = sss_tc_fqname2(sz_name, rctx->global_names, + domname, domname, name); + if (fq_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Conversion to fqname failed.\n"); + talloc_free(sz_name); + return ENOMEM; + } + + name = fq_name; + } + + to_sized_string(sz_name, name); + + *_sz_name = sz_name; + + return EOK; +} + +static errno_t +sss_nss_get_ad_name(TALLOC_CTX *mem_ctx, + struct resp_ctx *rctx, + struct cache_req_result *result, + struct sized_string **_sz_name) +{ + struct ldb_message *msg = result->msgs[0]; + const char *name; + errno_t ret; + + if (result->well_known_object) { + return sss_nss_get_well_known_name(mem_ctx, rctx, result, _sz_name); + } + + name = ldb_msg_find_attr_as_string(msg, ORIGINALAD_PREFIX SYSDB_NAME, + NULL); + if (name == NULL) { + name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); + } + + if (name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing name.\n"); + return EINVAL; + } + + ret = sized_output_name(mem_ctx, rctx, name, result->domain, _sz_name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Unable to create sized name [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + return EOK; +} + +errno_t +sss_nss_protocol_fill_single_name(struct sss_nss_ctx *nss_ctx, + struct sss_nss_cmd_ctx *cmd_ctx, + struct sss_packet *packet, + struct cache_req_result *result) +{ + if (result->ldb_result->count > 1) { + DEBUG(SSSDBG_TRACE_FUNC, "Lookup returned more than one result " + "but only one was expected.\n"); + return EEXIST; + } + + return sss_nss_protocol_fill_name(nss_ctx, cmd_ctx, packet, result); +} + +errno_t +sss_nss_protocol_fill_name(struct sss_nss_ctx *nss_ctx, + struct sss_nss_cmd_ctx *cmd_ctx, + struct sss_packet *packet, + struct cache_req_result *result) +{ + struct sized_string *sz_name; + enum sss_id_type id_type; + size_t rp = 0; + size_t body_len; + uint8_t *body; + errno_t ret; + + ret = sss_nss_get_id_type(cmd_ctx, result, &id_type); + if (ret != EOK) { + return ret; + } + + ret = sss_nss_get_ad_name(cmd_ctx, nss_ctx->rctx, result, &sz_name); + if (ret != EOK) { + return ret; + } + + ret = sss_packet_grow(packet, sz_name->len + 3 * sizeof(uint32_t)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_packet_grow failed.\n"); + talloc_free(sz_name); + return ret; + } + + sss_packet_get_body(packet, &body, &body_len); + + SAFEALIGN_SET_UINT32(&body[rp], 1, &rp); /* Num results. */ + SAFEALIGN_SET_UINT32(&body[rp], 0, &rp); /* Reserved. */ + SAFEALIGN_SET_UINT32(&body[rp], id_type, &rp); + SAFEALIGN_SET_STRING(&body[rp], sz_name->str, sz_name->len, &rp); + + talloc_free(sz_name); + + return EOK; +} + +errno_t +sss_nss_protocol_fill_id(struct sss_nss_ctx *nss_ctx, + struct sss_nss_cmd_ctx *cmd_ctx, + struct sss_packet *packet, + struct cache_req_result *result) +{ + struct ldb_message *msg = result->msgs[0]; + enum sss_id_type id_type; + uint64_t id64; + uint32_t id; + const char *sid = NULL; + struct sized_string sid_key; + size_t rp = 0; + size_t body_len; + uint8_t *body; + errno_t ret; + + if (result->ldb_result == NULL) { + /* This was a well known SID. This is currently unsupported with id. */ + return EINVAL; + } + + ret = sss_nss_get_id_type(cmd_ctx, result, &id_type); + if (ret != EOK) { + return ret; + } + + if (id_type == SSS_ID_TYPE_GID) { + id64 = ldb_msg_find_attr_as_uint64(msg, SYSDB_GIDNUM, 0); + } else { + id64 = ldb_msg_find_attr_as_uint64(msg, SYSDB_UIDNUM, 0); + } + + if (id64 == 0 || id64 >= UINT32_MAX) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid POSIX ID.\n"); + return EINVAL; + } + + id = (uint32_t)id64; + + ret = sss_packet_grow(packet, 4 * sizeof(uint32_t)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_packet_grow failed.\n"); + return ret; + } + + sss_packet_get_body(packet, &body, &body_len); + + SAFEALIGN_SET_UINT32(&body[rp], 1, &rp); /* Num results. */ + SAFEALIGN_SET_UINT32(&body[rp], 0, &rp); /* Reserved. */ + SAFEALIGN_SET_UINT32(&body[rp], id_type, &rp); + SAFEALIGN_SET_UINT32(&body[rp], id, &rp); + + if (nss_ctx->sid_mc_ctx != NULL) { + /* no need to check for SSS_NSS_EX_FLAG_INVALIDATE_CACHE since + * SID related requests don't support 'flags' + */ + sid = ldb_msg_find_attr_as_string(msg, SYSDB_SID_STR, NULL); + if (!sid) { + DEBUG(SSSDBG_OP_FAILURE, "Missing SID?!\n"); + return EOK; + } + to_sized_string(&sid_key, sid); + ret = sss_mmap_cache_sid_store(&nss_ctx->sid_mc_ctx, &sid_key, id, id_type, + cmd_ctx->type != CACHE_REQ_OBJECT_BY_ID); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to store SID='%s' / ID=%d in mmap cache [%d]: %s!\n", + sid, id, ret, sss_strerror(ret)); + } + } + + return EOK; +} + +errno_t +sss_nss_protocol_fill_name_list_all_domains(struct sss_nss_ctx *nss_ctx, + struct sss_nss_cmd_ctx *cmd_ctx, + struct sss_packet *packet, + struct cache_req_result **results) +{ + enum sss_id_type *id_types; + size_t rp = 0; + size_t body_len; + uint8_t *body; + errno_t ret; + struct sized_string *sz_names; + size_t len; + size_t c; + const char *tmp_str; + size_t d; + size_t total = 0; + size_t iter = 0; + + if (results == NULL) { + return EINVAL; + } + + for (d = 0; results[d] != NULL; d++) { + total += results[d]->count; + } + + sz_names = talloc_array(cmd_ctx, struct sized_string, total); + if (sz_names == NULL) { + return ENOMEM; + } + + id_types = talloc_array(cmd_ctx, enum sss_id_type, total); + if (id_types == NULL) { + return ENOMEM; + } + + len = 0; + for (d = 0; results[d] != NULL; d++) { + for (c = 0; c < results[d]->count; c++) { + ret = sss_nss_get_id_type(cmd_ctx, results[d], &(id_types[iter])); + if (ret != EOK) { + return ret; + } + + tmp_str = sss_get_name_from_msg(results[d]->domain, + results[d]->msgs[c]); + if (tmp_str == NULL) { + return EINVAL; + } + to_sized_string(&(sz_names[iter]), tmp_str); + + len += sz_names[iter].len; + iter++; + } + } + + len += (2 + total) * sizeof(uint32_t); + + ret = sss_packet_grow(packet, len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_packet_grow failed.\n"); + return ret; + } + + sss_packet_get_body(packet, &body, &body_len); + + SAFEALIGN_SET_UINT32(&body[rp], total, &rp); /* Num results. */ + SAFEALIGN_SET_UINT32(&body[rp], 0, &rp); /* Reserved. */ + for (c = 0; c < total; c++) { + SAFEALIGN_SET_UINT32(&body[rp], id_types[c], &rp); + SAFEALIGN_SET_STRING(&body[rp], sz_names[c].str, sz_names[c].len, + &rp); + } + + return EOK; +} diff --git a/src/responder/nss/nss_protocol_subid.c b/src/responder/nss/nss_protocol_subid.c new file mode 100644 index 0000000..c06ab0b --- /dev/null +++ b/src/responder/nss/nss_protocol_subid.c @@ -0,0 +1,60 @@ +/* + Copyright (C) 2021 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 "responder/nss/nss_protocol.h" + +errno_t +sss_nss_protocol_fill_subid_ranges(struct sss_nss_ctx *nss_ctx, + struct sss_nss_cmd_ctx *cmd_ctx, + struct sss_packet *packet, + struct cache_req_result *result) +{ + static const uint32_t one = 1; + errno_t ret; + uint8_t *body; + size_t body_len; + size_t rp = 0; + uint32_t gid, uid, gidCount, uidCount; + + if (!result->count || !result->msgs) { + return ENOENT; + } + + uid = ldb_msg_find_attr_as_uint(result->msgs[0], SYSDB_SUBID_UID_NUMBER, 0); + uidCount = ldb_msg_find_attr_as_uint(result->msgs[0], SYSDB_SUBID_UID_COUND, 0); + gid = ldb_msg_find_attr_as_uint(result->msgs[0], SYSDB_SUBID_GID_NUMBER, 0); + gidCount = ldb_msg_find_attr_as_uint(result->msgs[0], SYSDB_SUBID_GID_COUNT, 0); + if (!uid || !gid || !gidCount || !uidCount) { + return ENOENT; + } + + /* only single uid & gid range is expected currently */ + ret = sss_packet_grow(packet, (2 + 2*2) * sizeof(uint32_t)); + if (ret != EOK) { + return ret; + } + + sss_packet_get_body(packet, &body, &body_len); + SAFEALIGN_COPY_UINT32(&body[rp], &one, &rp); + SAFEALIGN_COPY_UINT32(&body[rp], &one, &rp); + SAFEALIGN_COPY_UINT32(&body[rp], &uid, &rp); + SAFEALIGN_COPY_UINT32(&body[rp], &uidCount, &rp); + SAFEALIGN_COPY_UINT32(&body[rp], &gid, &rp); + SAFEALIGN_COPY_UINT32(&body[rp], &gidCount, &rp); + + return EOK; +} diff --git a/src/responder/nss/nss_protocol_svcent.c b/src/responder/nss/nss_protocol_svcent.c new file mode 100644 index 0000000..68e20af --- /dev/null +++ b/src/responder/nss/nss_protocol_svcent.c @@ -0,0 +1,270 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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 "db/sysdb.h" +#include "db/sysdb_services.h" +#include "responder/nss/nss_protocol.h" + +static errno_t +sss_nss_get_svcent(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + struct ldb_message *msg, + const char *requested_protocol, + struct sized_string *_name, + struct sized_string *_protocol, + uint16_t *_port) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_message_element *el; + const char *protocol; + const char *name; + uint16_t port; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + /* Get name. */ + name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); + if (name == NULL) { + ret = ERR_INTERNAL; + goto done; + } + + name = sss_get_cased_name(tmp_ctx, name, domain->case_preserve); + if (name == NULL) { + ret = ENOMEM; + goto done; + } + + /* Get port. */ + port = (uint16_t)ldb_msg_find_attr_as_uint(msg, SYSDB_SVC_PORT, 0); + if (port == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "No port for service [%s]\n", name); + ret = EINVAL; + goto done; + } + + /* Get protocol. + * + * Use the requested protocol if present, otherwise take the + * first protocol returned by the sysdb. */ + if (requested_protocol != NULL) { + protocol = requested_protocol; + } else { + el = ldb_msg_find_element(msg, SYSDB_SVC_PROTO); + if (el == NULL || el->num_values == 0) { + ret = EINVAL; + goto done; + } + + protocol = (const char *)el->values[0].data; + if (protocol == NULL) { + ret = ERR_INTERNAL; + goto done; + } + } + + protocol = sss_get_cased_name(tmp_ctx, protocol, domain->case_preserve); + if (protocol == NULL) { + ret = ENOMEM; + goto done; + } + + /* Set output variables. */ + + talloc_steal(mem_ctx, name); + talloc_steal(mem_ctx, protocol); + + to_sized_string(_name, name); + to_sized_string(_protocol, protocol); + *_port = port; + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t +sss_nss_get_svc_aliases(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + struct ldb_message *msg, + const char *name, + struct sized_string **_aliases, + uint32_t *_num_aliases) +{ + struct ldb_message_element *el; + struct sized_string *aliases = NULL; + uint32_t num_aliases; + const char *alias; + errno_t ret; + int i; + + el = ldb_msg_find_element(msg, SYSDB_NAME_ALIAS); + if (el == NULL) { + *_num_aliases = 0; + *_aliases = NULL; + ret = EOK; + goto done; + } + + aliases = talloc_zero_array(mem_ctx, struct sized_string, + el->num_values + 1); + if (aliases == NULL) { + ret = ENOMEM; + goto done; + } + + num_aliases = 0; + for (i = 0; i < el->num_values; i++) { + alias = (const char *)el->values[i].data; + + if (sss_string_equal(domain->case_sensitive, alias, name)) { + continue; + } + + /* Element value remains in the message, we don't need to strdup it. */ + to_sized_string(&aliases[num_aliases], alias); + num_aliases++; + } + + *_aliases = aliases; + *_num_aliases = num_aliases; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(aliases); + } + + return ret; +} + +errno_t +sss_nss_protocol_fill_svcent(struct sss_nss_ctx *nss_ctx, + struct sss_nss_cmd_ctx *cmd_ctx, + struct sss_packet *packet, + struct cache_req_result *result) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_message *msg; + struct sized_string name; + struct sized_string protocol; + struct sized_string *aliases; + uint32_t num_aliases; + uint16_t port; + uint32_t num_results; + size_t rp; + size_t body_len; + uint8_t *body; + int i; + int j; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + /* First two fields (length and reserved), filled up later. */ + ret = sss_packet_grow(packet, 2 * sizeof(uint32_t)); + if (ret != EOK) { + return ret; + } + + rp = 2 * sizeof(uint32_t); + + num_results = 0; + for (i = 0; i < result->count; i++) { + talloc_free_children(tmp_ctx); + msg = result->msgs[i]; + + ret = sss_nss_get_svcent(tmp_ctx, result->domain, msg, + cmd_ctx->svc_protocol, &name, &protocol, &port); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Unable to get service information, skipping... [%d]: %s\n", + ret, sss_strerror(ret)); + continue; + } + + ret = sss_nss_get_svc_aliases(tmp_ctx, result->domain, msg, name.str, + &aliases, &num_aliases); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Unable to get service aliases, skipping... [%d]: %s\n", + ret, sss_strerror(ret)); + continue; + } + + /* Adjust packet size. */ + + ret = sss_packet_grow(packet, 2 * sizeof(uint16_t) + sizeof(uint32_t) + + name.len + protocol.len); + if (ret != EOK) { + goto done; + } + + sss_packet_get_body(packet, &body, &body_len); + + /* Fill packet. */ + + SAFEALIGN_SET_UINT32(&body[rp], (uint32_t)htons(port), &rp); + SAFEALIGN_SET_UINT32(&body[rp], num_aliases, &rp); + SAFEALIGN_SET_STRING(&body[rp], name.str, name.len, &rp); + SAFEALIGN_SET_STRING(&body[rp], protocol.str, protocol.len, &rp); + + /* Store aliases. */ + for (j = 0; j < num_aliases; j++) { + ret = sss_packet_grow(packet, aliases[j].len); + if (ret != EOK) { + goto done; + } + sss_packet_get_body(packet, &body, &body_len); + + SAFEALIGN_SET_STRING(&body[rp], aliases[j].str, aliases[j].len, + &rp); + } + + num_results++; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + if (ret != EOK) { + sss_packet_set_size(packet, 0); + return ret; + } + + sss_packet_get_body(packet, &body, &body_len); + SAFEALIGN_COPY_UINT32(body, &num_results, NULL); + SAFEALIGN_SETMEM_UINT32(body + sizeof(uint32_t), 0, NULL); /* reserved */ + + return EOK; +} diff --git a/src/responder/nss/nss_utils.c b/src/responder/nss/nss_utils.c new file mode 100644 index 0000000..cec55e6 --- /dev/null +++ b/src/responder/nss/nss_utils.c @@ -0,0 +1,38 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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 <talloc.h> +#include <ldb.h> + +#include "util/util.h" +#include "confdb/confdb.h" +#include "responder/common/responder.h" +#include "responder/nss/nss_private.h" + +const char * +sss_nss_get_pwfield(struct sss_nss_ctx *nctx, + struct sss_domain_info *dom) +{ + if (dom->pwfield != NULL) { + return dom->pwfield; + } + + return nctx->pwfield; +} diff --git a/src/responder/nss/nsssrv.c b/src/responder/nss/nsssrv.c new file mode 100644 index 0000000..4673a64 --- /dev/null +++ b/src/responder/nss/nsssrv.c @@ -0,0 +1,740 @@ +/* + SSSD + + NSS Responder + + Copyright (C) Simo Sorce <ssorce@redhat.com> 2008 + + 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 <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <string.h> +#include <sys/time.h> +#include <errno.h> +#include <popt.h> +#include <dbus/dbus.h> + +#include "util/util.h" +#include "util/sss_ptr_hash.h" +#include "util/mmap_cache.h" +#include "responder/nss/nss_private.h" +#include "responder/nss/nss_iface.h" +#include "responder/nss/nsssrv_mmap_cache.h" +#include "responder/common/negcache.h" +#include "db/sysdb.h" +#include "confdb/confdb.h" +#include "responder/common/responder_packet.h" +#include "responder/common/responder.h" +#include "providers/data_provider.h" +#include "util/util_sss_idmap.h" +#include "sss_iface/sss_iface_async.h" + +#define DEFAULT_PWFIELD "*" +#define DEFAULT_NSS_FD_LIMIT 8192 + +static errno_t +sss_nss_clear_memcache(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct sss_nss_ctx *nctx) +{ + int memcache_timeout; + errno_t ret; + + if (access(SSS_NSS_MCACHE_DIR"/"CLEAR_MC_FLAG, F_OK) < 0) { + ret = errno; + if (ret == ENOENT) { + DEBUG(SSSDBG_TRACE_FUNC, + "CLEAR_MC_FLAG not found. Nothing to do.\n"); + return EOK; /* Most probably log rotation SIGHUP to monitor */ + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to check existence of "CLEAR_MC_FLAG": %s.\n", + strerror(ret)); + return ret; + } + } + + /* + * CLEAR_MC_FLAG flag file found. + * This file existance indicates that SIGHUP was called by sss_cache + * as trigger for the memory cache cleanup. + * sss_cache is waiting for CLEAR_MC_FLAG file deletion + * as confirmation that memory cache cleaning has finished. + */ + + ret = confdb_get_int(nctx->rctx->cdb, + CONFDB_NSS_CONF_ENTRY, + CONFDB_MEMCACHE_TIMEOUT, + 300, &memcache_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Unable to get memory cache entry timeout [%s].\n", + CONFDB_MEMCACHE_TIMEOUT); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Clearing memory caches.\n"); + ret = sss_mmap_cache_reinit(nctx, nctx->mc_uid, nctx->mc_gid, + -1, /* keep current size */ + (time_t) memcache_timeout, + &nctx->pwd_mc_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "passwd mmap cache invalidation failed\n"); + goto done; + } + + ret = sss_mmap_cache_reinit(nctx, nctx->mc_uid, nctx->mc_gid, + -1, /* keep current size */ + (time_t) memcache_timeout, + &nctx->grp_mc_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "group mmap cache invalidation failed\n"); + goto done; + } + + ret = sss_mmap_cache_reinit(nctx, nctx->mc_uid, nctx->mc_gid, + -1, /* keep current size */ + (time_t)memcache_timeout, + &nctx->initgr_mc_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "initgroups mmap cache invalidation failed\n"); + goto done; + } + +done: + if (unlink(SSS_NSS_MCACHE_DIR"/"CLEAR_MC_FLAG) != 0) { + if (errno != ENOENT) + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to unlink file: %s.\n", + strerror(errno)); + } + return ret; +} + +static errno_t +sss_nss_clear_negcache(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct sss_nss_ctx *nctx) +{ + errno_t ret; + + DEBUG(SSSDBG_TRACE_FUNC, "Clearing negative cache non-permament entries\n"); + + ret = sss_ncache_reset_users(nctx->rctx->ncache); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Negative cache clearing users failed\n"); + goto done; + } + + ret = sss_ncache_reset_groups(nctx->rctx->ncache); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Negative cache clearing groups failed\n"); + goto done; + } + +done: + return ret; +} + +static errno_t +sss_nss_clear_netgroup_hash_table(TALLOC_CTX *mem_ctx, + struct sbus_request *sbus_req, + struct sss_nss_ctx *nss_ctx) +{ + DEBUG(SSSDBG_TRACE_FUNC, "Invalidating netgroup hash table\n"); + + sss_ptr_hash_delete_all(nss_ctx->netgrent, false); + + return EOK; +} + +static int sss_nss_get_config(struct sss_nss_ctx *nctx, + struct confdb_ctx *cdb) +{ + int ret; + char *tmp_str; + static const char *orig_attrs[] = { SYSDB_SID_STR, + ORIGINALAD_PREFIX SYSDB_NAME, + ORIGINALAD_PREFIX SYSDB_UIDNUM, + ORIGINALAD_PREFIX SYSDB_GIDNUM, + ORIGINALAD_PREFIX SYSDB_HOMEDIR, + ORIGINALAD_PREFIX SYSDB_GECOS, + ORIGINALAD_PREFIX SYSDB_SHELL, + SYSDB_UPN, + SYSDB_DEFAULT_OVERRIDE_NAME, + SYSDB_AD_ACCOUNT_EXPIRES, + SYSDB_AD_USER_ACCOUNT_CONTROL, + SYSDB_SSH_PUBKEY, + SYSDB_USER_CERT, + SYSDB_USER_EMAIL, + SYSDB_ORIG_DN, + SYSDB_ORIG_MEMBEROF, + NULL }; + + ret = confdb_get_int(cdb, CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_ENUM_CACHE_TIMEOUT, 120, + &nctx->enum_cache_timeout); + if (ret != EOK) goto done; + + ret = confdb_get_bool(cdb, CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_FILTER_USERS_IN_GROUPS, true, + &nctx->filter_users_in_groups); + if (ret != EOK) goto done; + + ret = confdb_get_int(cdb, CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_ENTRY_CACHE_NOWAIT_PERCENTAGE, 50, + &nctx->cache_refresh_percent); + if (ret != EOK) goto done; + if (nctx->cache_refresh_percent < 0 || + nctx->cache_refresh_percent > 99) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Configuration error: entry_cache_nowait_percentage is " + "invalid. Disabling feature.\n"); + nctx->cache_refresh_percent = 0; + } + + ret = sss_ncache_prepopulate(nctx->rctx->ncache, cdb, nctx->rctx); + if (ret != EOK) { + goto done; + } + + ret = confdb_get_string(cdb, nctx, CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_PWFIELD, DEFAULT_PWFIELD, + &nctx->pwfield); + if (ret != EOK) goto done; + + ret = confdb_get_string(cdb, nctx, CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_OVERRIDE_HOMEDIR, NULL, + &nctx->override_homedir); + if (ret != EOK) goto done; + + ret = confdb_get_string(cdb, nctx, CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_FALLBACK_HOMEDIR, NULL, + &nctx->fallback_homedir); + if (ret != EOK) goto done; + + ret = confdb_get_string(cdb, nctx, CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_HOMEDIR_SUBSTRING, + CONFDB_DEFAULT_HOMEDIR_SUBSTRING, + &nctx->homedir_substr); + if (ret != EOK) goto done; + + + ret = confdb_get_string(cdb, nctx, CONFDB_NSS_CONF_ENTRY, + CONFDB_IFP_USER_ATTR_LIST, NULL, &tmp_str); + if (ret != EOK) goto done; + + if (tmp_str == NULL) { + ret = confdb_get_string(cdb, nctx, CONFDB_IFP_CONF_ENTRY, + CONFDB_IFP_USER_ATTR_LIST, NULL, &tmp_str); + if (ret != EOK) goto done; + } + + if (tmp_str != NULL) { + nctx->extra_attributes = parse_attr_list_ex(nctx, tmp_str, NULL); + if (nctx->extra_attributes == NULL) { + ret = ENOMEM; + goto done; + } + } + + ret = add_strings_lists_ex(nctx, nctx->extra_attributes, orig_attrs, false, + true, &nctx->full_attribute_list); + if (ret != EOK) { + ret = ENOMEM; + goto done; + } + + ret = 0; +done: + return ret; +} + +static int setup_memcaches(struct sss_nss_ctx *nctx) +{ + /* Default memcache sizes */ + static const size_t SSS_MC_CACHE_SLOTS_PER_MB = 1024*1024/MC_SLOT_SIZE; + static const size_t SSS_MC_CACHE_PASSWD_SIZE = 8; + static const size_t SSS_MC_CACHE_GROUP_SIZE = 6; + static const size_t SSS_MC_CACHE_INITGROUP_SIZE = 10; + static const size_t SSS_MC_CACHE_SID_SIZE = 6; + + int ret; + int memcache_timeout; + int mc_size_passwd; + int mc_size_group; + int mc_size_initgroups; + int mc_size_sid; + + /* Remove the CLEAR_MC_FLAG file if exists. */ + ret = unlink(SSS_NSS_MCACHE_DIR"/"CLEAR_MC_FLAG); + if (ret != 0 && errno != ENOENT) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to unlink file [%s]. This can cause memory cache to " + "be purged when next log rotation is requested. %d: %s\n", + SSS_NSS_MCACHE_DIR"/"CLEAR_MC_FLAG, ret, strerror(ret)); + } + + ret = confdb_get_int(nctx->rctx->cdb, + CONFDB_NSS_CONF_ENTRY, + CONFDB_MEMCACHE_TIMEOUT, + 300, &memcache_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to get 'memcache_timeout' option from confdb.\n"); + return ret; + } + + /* Get all memcache sizes from confdb (pwd, grp, initgr, sid) */ + + ret = confdb_get_int(nctx->rctx->cdb, + CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_MEMCACHE_SIZE_PASSWD, + SSS_MC_CACHE_PASSWD_SIZE, + &mc_size_passwd); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to get '"CONFDB_NSS_MEMCACHE_SIZE_PASSWD + "' option from confdb.\n"); + return ret; + } + + ret = confdb_get_int(nctx->rctx->cdb, + CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_MEMCACHE_SIZE_GROUP, + SSS_MC_CACHE_GROUP_SIZE, + &mc_size_group); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to get '"CONFDB_NSS_MEMCACHE_SIZE_GROUP + "' option from confdb.\n"); + return ret; + } + + ret = confdb_get_int(nctx->rctx->cdb, + CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_MEMCACHE_SIZE_INITGROUPS, + SSS_MC_CACHE_INITGROUP_SIZE, + &mc_size_initgroups); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to get '"CONFDB_NSS_MEMCACHE_SIZE_INITGROUPS + "' option from confdb.\n"); + return ret; + } + + ret = confdb_get_int(nctx->rctx->cdb, + CONFDB_NSS_CONF_ENTRY, + CONFDB_NSS_MEMCACHE_SIZE_SID, + SSS_MC_CACHE_SID_SIZE, + &mc_size_sid); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to get '"CONFDB_NSS_MEMCACHE_SIZE_SID + "' option from confdb.\n"); + return ret; + } + + /* Initialize the fast in-memory caches if they were not disabled */ + + ret = sss_mmap_cache_init(nctx, "passwd", + nctx->mc_uid, nctx->mc_gid, + SSS_MC_PASSWD, + mc_size_passwd * SSS_MC_CACHE_SLOTS_PER_MB, + (time_t)memcache_timeout, + &nctx->pwd_mc_ctx); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to initialize passwd mmap cache: '%s'\n", + sss_strerror(ret)); + } + + ret = sss_mmap_cache_init(nctx, "group", + nctx->mc_uid, nctx->mc_gid, + SSS_MC_GROUP, + mc_size_group * SSS_MC_CACHE_SLOTS_PER_MB, + (time_t)memcache_timeout, + &nctx->grp_mc_ctx); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to initialize group mmap cache: '%s'\n", + sss_strerror(ret)); + } + + ret = sss_mmap_cache_init(nctx, "initgroups", + nctx->mc_uid, nctx->mc_gid, + SSS_MC_INITGROUPS, + mc_size_initgroups * SSS_MC_CACHE_SLOTS_PER_MB, + (time_t)memcache_timeout, + &nctx->initgr_mc_ctx); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to initialize initgroups mmap cache: '%s'\n", + sss_strerror(ret)); + } + + ret = sss_mmap_cache_init(nctx, "sid", + nctx->mc_uid, nctx->mc_gid, + SSS_MC_SID, + mc_size_sid * SSS_MC_CACHE_SLOTS_PER_MB, + (time_t)memcache_timeout, + &nctx->sid_mc_ctx); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to initialize sid mmap cache: '%s'\n", + sss_strerror(ret)); + } + + return EOK; +} + +static errno_t +sss_nss_register_service_iface(struct sss_nss_ctx *nss_ctx, + struct resp_ctx *rctx) +{ + errno_t ret; + + SBUS_INTERFACE(iface_svc, + sssd_service, + SBUS_METHODS( + SBUS_SYNC(METHOD, sssd_service, rotateLogs, responder_logrotate, rctx), + SBUS_SYNC(METHOD, sssd_service, clearEnumCache, sss_nss_clear_netgroup_hash_table, nss_ctx), + SBUS_SYNC(METHOD, sssd_service, clearMemcache, sss_nss_clear_memcache, nss_ctx), + SBUS_SYNC(METHOD, sssd_service, clearNegcache, sss_nss_clear_negcache, nss_ctx) + ), + SBUS_SIGNALS(SBUS_NO_SIGNALS), + SBUS_PROPERTIES( + SBUS_SYNC(GETTER, sssd_service, debug_level, generic_get_debug_level, NULL), + SBUS_SYNC(SETTER, sssd_service, debug_level, generic_set_debug_level, NULL) + ) + ); + + ret = sbus_connection_add_path(rctx->mon_conn, SSS_BUS_PATH, &iface_svc); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to register service interface" + "[%d]: %s\n", ret, sss_strerror(ret)); + } + + return ret; +} + +static int sssd_supplementary_group(struct sss_nss_ctx *nss_ctx) +{ + errno_t ret; + int size; + gid_t *supp_gids = NULL; + + /* + * We explicitly read the IDs of the SSSD user even though the server + * receives --uid and --gid by parameters to account for the case where + * the SSSD is compiled --with-sssd-user=sssd but the default of the + * user option is root (this is what RHEL does) + */ + ret = sss_user_by_name_or_uid(SSSD_USER, + &nss_ctx->mc_uid, + &nss_ctx->mc_gid); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Cannot get info on "SSSD_USER); + return ret; + } + + if (getgid() == nss_ctx->mc_gid) { + DEBUG(SSSDBG_TRACE_FUNC, "Already running as the sssd group\n"); + return EOK; + } + + size = getgroups(0, NULL); + if (size == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "Getgroups failed! (%d, %s)\n", + ret, sss_strerror(ret)); + return ret; + } + + if (size > 0) { + supp_gids = talloc_zero_array(NULL, gid_t, size); + if (supp_gids == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Allocation failed!\n"); + ret = ENOMEM; + goto done; + } + + size = getgroups(size, supp_gids); + if (size == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "Getgroups failed! (%d, %s)\n", + ret, sss_strerror(ret)); + goto done; + } + + for (int i = 0; i < size; i++) { + if (supp_gids[i] == nss_ctx->mc_gid) { + DEBUG(SSSDBG_TRACE_FUNC, + "Already assigned to the SSSD supplementary group\n"); + ret = EOK; + goto done; + } + } + } + + ret = setgroups(1, &nss_ctx->mc_gid); + if (ret != EOK) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, + "Cannot setgroups [%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; +done: + talloc_free(supp_gids); + return ret; +} + +int sss_nss_process_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct confdb_ctx *cdb) +{ + struct resp_ctx *rctx; + struct sss_cmd_table *nss_cmds; + struct be_conn *iter; + struct sss_nss_ctx *nctx; + int ret; + enum idmap_error_code err; + int fd_limit; + + nss_cmds = get_sss_nss_cmds(); + + ret = sss_process_init(mem_ctx, ev, cdb, + nss_cmds, + SSS_NSS_SOCKET_NAME, -1, NULL, -1, + CONFDB_NSS_CONF_ENTRY, + SSS_BUS_NSS, NSS_SBUS_SERVICE_NAME, + sss_nss_connection_setup, + &rctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "sss_process_init() failed\n"); + return ret; + } + + nctx = talloc_zero(rctx, struct sss_nss_ctx); + if (!nctx) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error initializing nss_ctx\n"); + ret = ENOMEM; + goto fail; + } + + nctx->rctx = rctx; + nctx->rctx->pvt_ctx = nctx; + + ret = sss_nss_get_config(nctx, cdb); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error getting nss config\n"); + goto fail; + } + + for (iter = nctx->rctx->be_conns; iter; iter = iter->next) { + ret = sss_nss_register_backend_iface(iter->conn, nctx); + if (ret != EOK) { + goto fail; + } + } + + err = sss_idmap_init(sss_idmap_talloc, nctx, sss_idmap_talloc_free, + &nctx->idmap_ctx); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_FATAL_FAILURE, "sss_idmap_init failed.\n"); + ret = EFAULT; + goto fail; + } + + nctx->pwent = talloc_zero(nctx, struct sss_nss_enum_ctx); + if (nctx->pwent == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to initialize pwent context!\n"); + ret = ENOMEM; + goto fail; + } + + nctx->grent = talloc_zero(nctx, struct sss_nss_enum_ctx); + if (nctx->grent == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to initialize grent context!\n"); + ret = ENOMEM; + goto fail; + } + + nctx->svcent = talloc_zero(nctx, struct sss_nss_enum_ctx); + if (nctx->svcent == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to initialize svcent context!\n"); + ret = ENOMEM; + goto fail; + } + + nctx->netgrent = sss_ptr_hash_create(nctx, NULL, NULL); + if (nctx->netgrent == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to initialize netgroups table!\n"); + ret = EFAULT; + goto fail; + } + + nctx->hostent = talloc_zero(nctx, struct sss_nss_enum_ctx); + if (nctx->hostent == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to initialize hostent context!\n"); + ret = ENOMEM; + goto fail; + } + + nctx->netent = talloc_zero(nctx, struct sss_nss_enum_ctx); + if (nctx->netent == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unable to initialize netent context!\n"); + ret = ENOMEM; + goto fail; + } + + /* + * Adding the NSS process to the SSSD supplementary group avoids + * dac_override AVC messages from SELinux in case sssd_nss runs + * as root and tries to write to memcache owned by sssd:sssd + */ + ret = sssd_supplementary_group(nctx); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot add process to the sssd supplementary group [%d]: %s\n", + ret, sss_strerror(ret)); + goto fail; + } + + ret = setup_memcaches(nctx); + if (ret != EOK) { + goto fail; + } + + /* Set up file descriptor limits */ + ret = confdb_get_int(nctx->rctx->cdb, + CONFDB_NSS_CONF_ENTRY, + CONFDB_SERVICE_FD_LIMIT, + DEFAULT_NSS_FD_LIMIT, + &fd_limit); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to set up file descriptor limit\n"); + goto fail; + } + responder_set_fd_limit(fd_limit); + + ret = schedule_get_domains_task(rctx, rctx->ev, rctx, nctx->rctx->ncache, + NULL, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "schedule_get_domains_tasks failed.\n"); + goto fail; + } + + /* The responder is initialized. Now tell it to the monitor. */ + ret = sss_monitor_service_init(rctx, rctx->ev, SSS_BUS_NSS, + NSS_SBUS_SERVICE_NAME, + NSS_SBUS_SERVICE_VERSION, MT_SVC_SERVICE, + &rctx->last_request_time, &rctx->mon_conn); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error setting up message bus\n"); + goto fail; + } + + ret = sss_nss_register_service_iface(nctx, rctx); + if (ret != EOK) { + goto fail; + } + + DEBUG(SSSDBG_TRACE_FUNC, "NSS Initialization complete\n"); + + return EOK; + +fail: + talloc_free(rctx); + return ret; +} + +int main(int argc, const char *argv[]) +{ + int opt; + poptContext pc; + char *opt_logger = NULL; + struct main_context *main_ctx; + int ret; + uid_t uid = 0; + gid_t gid = 0; + + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_MAIN_OPTS + SSSD_LOGGER_OPTS + SSSD_SERVER_OPTS(uid, gid) + SSSD_RESPONDER_OPTS + POPT_TABLEEND + }; + + /* Set debug level to invalid value so we can decide if -d 0 was used. */ + debug_level = SSSDBG_INVALID; + + umask(DFL_RSP_UMASK); + + pc = poptGetContext(argv[0], argc, argv, long_options, 0); + while((opt = poptGetNextOpt(pc)) != -1) { + switch(opt) { + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + return 1; + } + } + + poptFreeContext(pc); + + /* set up things like debug, signals, daemonization, etc. */ + debug_log_file = "sssd_nss"; + DEBUG_INIT(debug_level, opt_logger); + + ret = server_setup("nss", true, 0, uid, gid, CONFDB_NSS_CONF_ENTRY, + &main_ctx, false); + if (ret != EOK) return 2; + + ret = die_if_parent_died(); + if (ret != EOK) { + /* This is not fatal, don't return */ + DEBUG(SSSDBG_OP_FAILURE, + "Could not set up to exit when parent process does\n"); + } + + ret = sss_nss_process_init(main_ctx, + main_ctx->event_ctx, + main_ctx->confdb_ctx); + if (ret != EOK) return 3; + + /* loop on main */ + server_loop(main_ctx); + + return 0; +} + diff --git a/src/responder/nss/nsssrv_mmap_cache.c b/src/responder/nss/nsssrv_mmap_cache.c new file mode 100644 index 0000000..cacdc7c --- /dev/null +++ b/src/responder/nss/nsssrv_mmap_cache.c @@ -0,0 +1,1626 @@ +/* + SSSD + + NSS Responder - Mmap Cache + + Copyright (C) Simo Sorce <ssorce@redhat.com> 2011 + + 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 "util/util.h" +#include "util/crypto/sss_crypto.h" +#include "confdb/confdb.h" +#include <sys/mman.h> +#include <fcntl.h> +#include "util/mmap_cache.h" +#include "sss_client/idmap/sss_nss_idmap.h" +#include "responder/nss/nss_private.h" +#include "responder/nss/nsssrv_mmap_cache.h" + +#define MC_NEXT_BARRIER(val) ((((val) + 1) & 0x00ffffff) | 0xf0000000) + +#define MC_RAISE_BARRIER(m) do { \ + m->b2 = MC_NEXT_BARRIER(m->b1); \ + __sync_synchronize(); \ +} while (0) + +#define MC_LOWER_BARRIER(m) do { \ + __sync_synchronize(); \ + m->b1 = m->b2; \ +} while (0) + +#define MC_RAISE_INVALID_BARRIER(m) do { \ + m->b2 = MC_INVALID_VAL; \ + __sync_synchronize(); \ +} while (0) + +struct sss_mc_ctx { + char *name; /* mmap cache name */ + enum sss_mc_type type; /* mmap cache type */ + char *file; /* mmap cache file name */ + int fd; /* file descriptor */ + + uid_t uid; /* User ID of owner */ + gid_t gid; /* Group ID of owner */ + + uint32_t seed; /* pseudo-random seed to avoid collision attacks */ + time_t valid_time_slot; /* maximum time the entry is valid in seconds */ + + void *mmap_base; /* base address of mmap */ + size_t mmap_size; /* total size of mmap */ + + uint32_t *hash_table; /* hash table address (in mmap) */ + uint32_t ht_size; /* size of hash table */ + + uint8_t *free_table; /* free list bitmaps */ + uint32_t ft_size; /* size of free table */ + uint32_t next_slot; /* the next slot after last allocation done via erasure */ + + uint8_t *data_table; /* data table address (in mmap) */ + uint32_t dt_size; /* size of data table */ +}; + +#define MC_FIND_BIT(base, num) \ + uint32_t n = (num); \ + uint8_t *b = (base) + n / 8; \ + uint8_t c = 0x80 >> (n % 8); + +#define MC_SET_BIT(base, num) do { \ + MC_FIND_BIT(base, num) \ + *b |= c; \ +} while (0) + +#define MC_CLEAR_BIT(base, num) do { \ + MC_FIND_BIT(base, num) \ + *b &= ~c; \ +} while (0) + +#define MC_PROBE_BIT(base, num, used) do { \ + MC_FIND_BIT(base, num) \ + if (*b & c) used = true; \ + else used = false; \ +} while (0) + +static inline +uint32_t sss_mc_next_slot_with_hash(struct sss_mc_rec *rec, + uint32_t hash) +{ + if (rec->hash1 == hash) { + return rec->next1; + } else if (rec->hash2 == hash) { + return rec->next2; + } else { + /* it should never happen. */ + return MC_INVALID_VAL; + } +} + +static inline +void sss_mc_chain_slot_to_record_with_hash(struct sss_mc_rec *rec, + uint32_t hash, + uint32_t slot) +{ + /* changing a single uint32_t is atomic, so there is no + * need to use barriers in this case */ + if (rec->hash1 == hash) { + rec->next1 = slot; + } else if (rec->hash2 == hash) { + rec->next2 = slot; + } +} + +/* This function will store corrupted memcache to disk for later + * analysis. */ +static void sss_mc_save_corrupted(struct sss_mc_ctx *mc_ctx) +{ + int err; + int fd = -1; + ssize_t written = -1; + char *file = NULL; + TALLOC_CTX *tmp_ctx; + + if (mc_ctx == NULL) { + DEBUG(SSSDBG_TRACE_FUNC, + "Cannot store uninitialized cache. Nothing to do.\n"); + return; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory.\n"); + return; + } + + file = talloc_asprintf(tmp_ctx, "%s_%s", + mc_ctx->file, "corrupted"); + if (file == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory.\n"); + goto done; + } + + /* We will always store only the last problematic cache state */ + fd = creat(file, 0600); + if (fd == -1) { + err = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to open file '%s' [%d]: %s\n", + file, err, strerror(err)); + goto done; + } + + written = sss_atomic_write_s(fd, mc_ctx->mmap_base, mc_ctx->mmap_size); + if (written != mc_ctx->mmap_size) { + if (written == -1) { + err = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "write() failed [%d]: %s\n", err, strerror(err)); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "write() returned %zd (expected (%zd))\n", + written, mc_ctx->mmap_size); + } + goto done; + } + + sss_log(SSS_LOG_NOTICE, + "Stored copy of corrupted mmap cache in file '%s\n'", file); +done: + if (fd != -1) { + close(fd); + if (written == -1) { + err = unlink(file); + if (err != 0) { + err = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to remove file '%s': %s.\n", file, + strerror(err)); + } + } + } + talloc_free(tmp_ctx); +} + +static uint32_t sss_mc_hash(struct sss_mc_ctx *mcc, + const char *key, size_t len) +{ + return murmurhash3(key, len, mcc->seed) % MC_HT_ELEMS(mcc->ht_size); +} + +static void sss_mc_add_rec_to_chain(struct sss_mc_ctx *mcc, + struct sss_mc_rec *rec, + uint32_t hash) +{ + struct sss_mc_rec *cur; + uint32_t slot; + + if (hash > MC_HT_ELEMS(mcc->ht_size)) { + /* Invalid hash. This should never happen, but better + * return than trying to access out of bounds memory */ + return; + } + + slot = mcc->hash_table[hash]; + if (slot == MC_INVALID_VAL) { + /* no previous record/collision, just add to hash table */ + mcc->hash_table[hash] = MC_PTR_TO_SLOT(mcc->data_table, rec); + return; + } + + do { + cur = MC_SLOT_TO_PTR(mcc->data_table, slot, struct sss_mc_rec); + if (cur == rec) { + /* rec already stored in hash chain */ + return; + } + slot = sss_mc_next_slot_with_hash(cur, hash); + } while (slot != MC_INVALID_VAL); + /* end of chain, append our record here */ + + slot = MC_PTR_TO_SLOT(mcc->data_table, rec); + sss_mc_chain_slot_to_record_with_hash(cur, hash, slot); +} + +static void sss_mc_rm_rec_from_chain(struct sss_mc_ctx *mcc, + struct sss_mc_rec *rec, + uint32_t hash) +{ + struct sss_mc_rec *prev = NULL; + struct sss_mc_rec *cur = NULL; + uint32_t slot; + + if (hash > MC_HT_ELEMS(mcc->ht_size)) { + /* It can happen if rec->hash1 and rec->hash2 was the same. + * or it is invalid hash. It is better to return + * than trying to access out of bounds memory + */ + return; + } + + slot = mcc->hash_table[hash]; + if (slot == MC_INVALID_VAL) { + /* record has already been removed. It may happen if rec->hash1 and + * rec->has2 are the same. (It is not very likely). + */ + return; + } + cur = MC_SLOT_TO_PTR(mcc->data_table, slot, struct sss_mc_rec); + if (cur == rec) { + mcc->hash_table[hash] = sss_mc_next_slot_with_hash(rec, hash); + } else { + slot = sss_mc_next_slot_with_hash(cur, hash); + while (slot != MC_INVALID_VAL) { + prev = cur; + cur = MC_SLOT_TO_PTR(mcc->data_table, slot, struct sss_mc_rec); + if (cur == rec) { + slot = sss_mc_next_slot_with_hash(cur, hash); + + sss_mc_chain_slot_to_record_with_hash(prev, hash, slot); + slot = MC_INVALID_VAL; + } else { + slot = sss_mc_next_slot_with_hash(cur, hash); + } + } + } +} + +static void sss_mc_free_slots(struct sss_mc_ctx *mcc, struct sss_mc_rec *rec) +{ + uint32_t slot; + uint32_t num; + uint32_t i; + + slot = MC_PTR_TO_SLOT(mcc->data_table, rec); + num = MC_SIZE_TO_SLOTS(rec->len); + for (i = 0; i < num; i++) { + MC_CLEAR_BIT(mcc->free_table, slot + i); + } +} + +static void sss_mc_invalidate_rec(struct sss_mc_ctx *mcc, + struct sss_mc_rec *rec) +{ + if (rec->b1 == MC_INVALID_VAL) { + /* record already invalid */ + return; + } + + /* Remove from hash chains */ + /* hash chain 1 */ + sss_mc_rm_rec_from_chain(mcc, rec, rec->hash1); + /* hash chain 2 */ + sss_mc_rm_rec_from_chain(mcc, rec, rec->hash2); + + /* Clear from free_table */ + sss_mc_free_slots(mcc, rec); + + /* Invalidate record fields */ + MC_RAISE_INVALID_BARRIER(rec); + memset(rec->data, MC_INVALID_VAL8, ((MC_SLOT_SIZE * MC_SIZE_TO_SLOTS(rec->len)) + - sizeof(struct sss_mc_rec))); + rec->len = MC_INVALID_VAL32; + rec->expire = MC_INVALID_VAL64; + rec->next1 = MC_INVALID_VAL32; + rec->next2 = MC_INVALID_VAL32; + rec->hash1 = MC_INVALID_VAL32; + rec->hash2 = MC_INVALID_VAL32; + MC_LOWER_BARRIER(rec); +} + +static bool sss_mc_is_valid_rec(struct sss_mc_ctx *mcc, struct sss_mc_rec *rec) +{ + struct sss_mc_rec *self; + uint32_t slot; + + if (((uint8_t *)rec < mcc->data_table) || + ((uint8_t *)rec > (mcc->data_table + mcc->dt_size - MC_SLOT_SIZE))) { + return false; + } + + if ((rec->b1 == MC_INVALID_VAL) || + (rec->b1 != rec->b2)) { + return false; + } + + if (!MC_CHECK_RECORD_LENGTH(mcc, rec)) { + return false; + } + + if (rec->expire == MC_INVALID_VAL64) { + return false; + } + + /* next record can be invalid if there are no next records */ + + if (rec->hash1 == MC_INVALID_VAL32) { + return false; + } else { + self = NULL; + slot = mcc->hash_table[rec->hash1]; + while (slot != MC_INVALID_VAL32 && self != rec) { + self = MC_SLOT_TO_PTR(mcc->data_table, slot, struct sss_mc_rec); + slot = sss_mc_next_slot_with_hash(self, rec->hash1); + } + if (self != rec) { + return false; + } + } + if (rec->hash2 != MC_INVALID_VAL32) { + self = NULL; + slot = mcc->hash_table[rec->hash2]; + while (slot != MC_INVALID_VAL32 && self != rec) { + self = MC_SLOT_TO_PTR(mcc->data_table, slot, struct sss_mc_rec); + slot = sss_mc_next_slot_with_hash(self, rec->hash2); + } + if (self != rec) { + return false; + } + } + + /* all tests passed */ + return true; +} + +static const char *mc_type_to_str(enum sss_mc_type type) +{ + switch (type) { + case SSS_MC_PASSWD: + return "PASSWD"; + case SSS_MC_GROUP: + return "GROUP"; + case SSS_MC_INITGROUPS: + return "INITGROUPS"; + case SSS_MC_SID: + return "SID"; + default: + return "-UNKNOWN-"; + } +} + +/* FIXME: This is a very simplistic, inefficient, memory allocator, + * it will just free the oldest entries regardless of expiration if it + * cycled the whole free bits map and found no empty slot */ +static errno_t sss_mc_find_free_slots(struct sss_mc_ctx *mcc, + int num_slots, uint32_t *free_slot) +{ + struct sss_mc_rec *rec; + uint32_t tot_slots; + uint32_t cur; + uint32_t i; + uint32_t t; + bool used; + + tot_slots = mcc->ft_size * 8; + + /* Try to find a free slot w/o removing anything first */ + /* FIXME: Is it really worth it? Maybe it is easier to + * just recycle the next set of slots? */ + if ((mcc->next_slot + num_slots) > tot_slots) { + cur = 0; + } else { + cur = mcc->next_slot; + } + + /* search for enough (num_slots) consecutive zero bits, indicating + * consecutive empty slots */ + for (i = 0; i < mcc->ft_size; i++) { + t = cur / 8; + /* if all full in this byte skip directly to the next */ + if (mcc->free_table[t] == 0xff) { + cur = ((cur + 8) & ~7); + if (cur >= tot_slots) { + cur = 0; + } + continue; + } + + /* at least one bit in this byte is marked as empty */ + for (t = ((cur + 8) & ~7) ; cur < t; cur++) { + MC_PROBE_BIT(mcc->free_table, cur, used); + if (!used) break; + } + /* check if we have enough slots before hitting the table end */ + if ((cur + num_slots) > tot_slots) { + cur = 0; + continue; + } + + /* check if we have at least num_slots empty starting from the first + * we found in the previous steps */ + for (t = cur + num_slots; cur < t; cur++) { + MC_PROBE_BIT(mcc->free_table, cur, used); + if (used) break; + } + if (cur == t) { + /* ok found num_slots consecutive free bits */ + *free_slot = cur - num_slots; + /* `mcc->next_slot` is not updated here intentionally. + * For details see discussion in https://github.com/SSSD/sssd/pull/999 + */ + return EOK; + } + } + + /* no free slots found, free occupied slots after next_slot */ + if ((mcc->next_slot + num_slots) > tot_slots) { + cur = 0; + } else { + cur = mcc->next_slot; + } + if (cur == 0) { + /* inform only once per full loop to avoid excessive spam */ + DEBUG(SSSDBG_IMPORTANT_INFO, "mmap cache of type '%s' is full\n", + mc_type_to_str(mcc->type)); + sss_log(SSS_LOG_NOTICE, "mmap cache of type '%s' is full, if you see " + "this message often then please consider increase of cache size", + mc_type_to_str(mcc->type)); + } + for (i = 0; i < num_slots; i++) { + MC_PROBE_BIT(mcc->free_table, cur + i, used); + if (used) { + /* the first used slot should be a record header, however we + * carefully check it is a valid header and hardfail if not */ + rec = MC_SLOT_TO_PTR(mcc->data_table, cur + i, struct sss_mc_rec); + if (!sss_mc_is_valid_rec(mcc, rec)) { + /* this is a fatal error, the caller should probably just + * invalidate the whole cache */ + return EFAULT; + } + /* next loop skip the whole record */ + i += MC_SIZE_TO_SLOTS(rec->len) - 1; + + /* finally invalidate record completely */ + sss_mc_invalidate_rec(mcc, rec); + } + } + + mcc->next_slot = cur + num_slots; + *free_slot = cur; + return EOK; +} + +static errno_t sss_mc_get_strs_offset(struct sss_mc_ctx *mcc, + size_t *_offset) +{ + switch (mcc->type) { + case SSS_MC_PASSWD: + *_offset = offsetof(struct sss_mc_pwd_data, strs); + return EOK; + case SSS_MC_GROUP: + *_offset = offsetof(struct sss_mc_grp_data, strs); + return EOK; + case SSS_MC_INITGROUPS: + *_offset = offsetof(struct sss_mc_initgr_data, gids); + return EOK; + case SSS_MC_SID: + *_offset = offsetof(struct sss_mc_sid_data, sid); + return EOK; + default: + DEBUG(SSSDBG_FATAL_FAILURE, "Unknown memory cache type.\n"); + return EINVAL; + } +} + +static errno_t sss_mc_get_strs_len(struct sss_mc_ctx *mcc, + struct sss_mc_rec *rec, + size_t *_len) +{ + switch (mcc->type) { + case SSS_MC_PASSWD: + *_len = ((struct sss_mc_pwd_data *)&rec->data)->strs_len; + return EOK; + case SSS_MC_GROUP: + *_len = ((struct sss_mc_grp_data *)&rec->data)->strs_len; + return EOK; + case SSS_MC_INITGROUPS: + *_len = ((struct sss_mc_initgr_data *)&rec->data)->data_len; + return EOK; + case SSS_MC_SID: + *_len = ((struct sss_mc_sid_data *)&rec->data)->sid_len; + return EOK; + default: + DEBUG(SSSDBG_FATAL_FAILURE, "Unknown memory cache type.\n"); + return EINVAL; + } +} + +static struct sss_mc_rec *sss_mc_find_record(struct sss_mc_ctx *mcc, + const struct sized_string *key) +{ + struct sss_mc_rec *rec = NULL; + uint32_t hash; + uint32_t slot; + rel_ptr_t name_ptr; + char *t_key; + size_t strs_offset; + size_t strs_len; + uint8_t *max_addr; + errno_t ret; + + hash = sss_mc_hash(mcc, key->str, key->len); + + slot = mcc->hash_table[hash]; + if (!MC_SLOT_WITHIN_BOUNDS(slot, mcc->dt_size)) { + return NULL; + } + + /* Get max address of data table. */ + max_addr = mcc->data_table + mcc->dt_size; + + ret = sss_mc_get_strs_offset(mcc, &strs_offset); + if (ret != EOK) { + return NULL; + } + + while (slot != MC_INVALID_VAL) { + if (!MC_SLOT_WITHIN_BOUNDS(slot, mcc->dt_size)) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Corrupted memcache. Slot number too big.\n"); + sss_mc_save_corrupted(mcc); + sss_mmap_cache_reset(mcc); + return NULL; + } + + rec = MC_SLOT_TO_PTR(mcc->data_table, slot, struct sss_mc_rec); + ret = sss_mc_get_strs_len(mcc, rec, &strs_len); + if (ret != EOK) { + return NULL; + } + + if (key->len > strs_len) { + /* The string cannot be in current record */ + slot = sss_mc_next_slot_with_hash(rec, hash); + continue; + } + + safealign_memcpy(&name_ptr, rec->data, sizeof(rel_ptr_t), NULL); + t_key = (char *)rec->data + name_ptr; + /* name_ptr must point to some data in the strs/gids area of the data + * payload. Since it is a pointer relative to rec->data it must be + * larger/equal to strs_offset and must be smaller then strs_offset + strs_len. + * Additionally the area must not end outside of the data table and + * t_key must be a zero-terminated string. */ + if (name_ptr < strs_offset + || name_ptr >= strs_offset + strs_len + || (uint8_t *)rec->data > max_addr + || strs_offset > max_addr - (uint8_t *)rec->data + || strs_len > max_addr - (uint8_t *)rec->data - strs_offset) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Corrupted memcache entry at slot %u. " + "name_ptr value is %u.\n", slot, name_ptr); + sss_mc_save_corrupted(mcc); + sss_mmap_cache_reset(mcc); + return NULL; + } + + if (strcmp(key->str, t_key) == 0) { + return rec; + } + + slot = sss_mc_next_slot_with_hash(rec, hash); + } + + return NULL; +} + +static errno_t sss_mc_get_record(struct sss_mc_ctx **_mcc, + size_t rec_len, + const struct sized_string *key, + struct sss_mc_rec **_rec) +{ + struct sss_mc_ctx *mcc = *_mcc; + struct sss_mc_rec *old_rec = NULL; + struct sss_mc_rec *rec; + int old_slots; + int num_slots; + uint32_t base_slot; + errno_t ret; + int i; + + num_slots = MC_SIZE_TO_SLOTS(rec_len); + + old_rec = sss_mc_find_record(mcc, key); + if (old_rec) { + old_slots = MC_SIZE_TO_SLOTS(old_rec->len); + + if (old_slots == num_slots) { + *_rec = old_rec; + return EOK; + } + + /* slot size changed, invalidate record and fall through to get a + * fully new record */ + sss_mc_invalidate_rec(mcc, old_rec); + } + + /* we are going to use more space, find enough free slots */ + ret = sss_mc_find_free_slots(mcc, num_slots, &base_slot); + if (ret != EOK) { + if (ret == EFAULT) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Fatal internal mmap cache error, invalidating cache!\n"); + (void)sss_mmap_cache_reinit(talloc_parent(mcc), + -1, -1, -1, -1, + _mcc); + } + return ret; + } + + rec = MC_SLOT_TO_PTR(mcc->data_table, base_slot, struct sss_mc_rec); + + /* mark as not valid yet */ + MC_RAISE_INVALID_BARRIER(rec); + rec->len = rec_len; + rec->next1 = MC_INVALID_VAL; + rec->next2 = MC_INVALID_VAL; + rec->padding = MC_INVALID_VAL; + MC_LOWER_BARRIER(rec); + + /* and now mark slots as used */ + for (i = 0; i < num_slots; i++) { + MC_SET_BIT(mcc->free_table, base_slot + i); + } + + *_rec = rec; + return EOK; +} + +static inline void sss_mmap_set_rec_header(struct sss_mc_ctx *mcc, + struct sss_mc_rec *rec, + size_t len, time_t ttl, + const char *key1, size_t key1_len, + const char *key2, size_t key2_len) +{ + rec->len = len; + rec->expire = time(NULL) + ttl; + rec->hash1 = sss_mc_hash(mcc, key1, key1_len); + rec->hash2 = sss_mc_hash(mcc, key2, key2_len); +} + +static inline void sss_mmap_chain_in_rec(struct sss_mc_ctx *mcc, + struct sss_mc_rec *rec) +{ + /* name first */ + sss_mc_add_rec_to_chain(mcc, rec, rec->hash1); + /* then uid/gid */ + sss_mc_add_rec_to_chain(mcc, rec, rec->hash2); +} + +/*************************************************************************** + * generic invalidation + ***************************************************************************/ + +static errno_t sss_mmap_cache_validate_or_reinit(struct sss_mc_ctx **_mcc); + +static errno_t sss_mmap_cache_invalidate(struct sss_mc_ctx **_mcc, + const struct sized_string *key) +{ + struct sss_mc_ctx *mcc; + struct sss_mc_rec *rec; + int ret; + + ret = sss_mmap_cache_validate_or_reinit(_mcc); + if (ret != EOK) { + return ret; + } + + mcc = *_mcc; + + rec = sss_mc_find_record(mcc, key); + if (rec == NULL) { + /* nothing to invalidate */ + return ENOENT; + } + + sss_mc_invalidate_rec(mcc, rec); + + return EOK; +} + +static errno_t sss_mmap_cache_validate_or_reinit(struct sss_mc_ctx **_mcc) +{ + struct sss_mc_ctx *mcc = *_mcc; + struct stat fdstat; + bool reinit = false; + errno_t ret; + + /* No mcc initialized? Memory cache may be disabled. */ + if (mcc == NULL || mcc->fd < 0) { + ret = EINVAL; + reinit = false; + goto done; + } + + if (fstat(mcc->fd, &fdstat) == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to stat memory cache [file=%s, fd=%d] [%d]: %s\n", + mcc->file, mcc->fd, ret, sss_strerror(ret)); + reinit = true; + goto done; + } + + if (fdstat.st_nlink == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Memory cache file was removed\n"); + ret = ENOENT; + reinit = true; + goto done; + } + + if (fdstat.st_size != mcc->mmap_size) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Memory cache is corrupted, invalid size [file=%s, fd=%d, " + "expected_size=%zu, real_size=%zu]\n", + mcc->file, mcc->fd, mcc->mmap_size, fdstat.st_size); + ret = EINVAL; + reinit = true; + goto done; + } + + ret = EOK; + reinit = false; + +done: + if (reinit) { + return sss_mmap_cache_reinit(talloc_parent(mcc), -1, -1, -1, -1, _mcc); + } + + return ret; +} + +/*************************************************************************** + * passwd map + ***************************************************************************/ + +errno_t sss_mmap_cache_pw_store(struct sss_mc_ctx **_mcc, + const struct sized_string *name, + const struct sized_string *pw, + uid_t uid, gid_t gid, + const struct sized_string *gecos, + const struct sized_string *homedir, + const struct sized_string *shell) +{ + struct sss_mc_ctx *mcc; + struct sss_mc_rec *rec; + struct sss_mc_pwd_data *data; + struct sized_string uidkey; + char uidstr[11]; + size_t data_len; + size_t rec_len; + size_t pos; + int ret; + + ret = sss_mmap_cache_validate_or_reinit(_mcc); + if (ret != EOK) { + return ret; + } + + mcc = *_mcc; + + ret = snprintf(uidstr, 11, "%ld", (long)uid); + if (ret > 10) { + return EINVAL; + } + to_sized_string(&uidkey, uidstr); + + data_len = name->len + pw->len + gecos->len + homedir->len + shell->len; + rec_len = sizeof(struct sss_mc_rec) + + sizeof(struct sss_mc_pwd_data) + + data_len; + if (rec_len > mcc->dt_size) { + return ENOMEM; + } + + ret = sss_mc_get_record(_mcc, rec_len, name, &rec); + if (ret != EOK) { + return ret; + } + + data = (struct sss_mc_pwd_data *)rec->data; + pos = 0; + + MC_RAISE_BARRIER(rec); + + /* header */ + sss_mmap_set_rec_header(mcc, rec, rec_len, mcc->valid_time_slot, + name->str, name->len, uidkey.str, uidkey.len); + + /* passwd struct */ + data->name = MC_PTR_DIFF(data->strs, data); + data->uid = uid; + data->gid = gid; + data->strs_len = data_len; + memcpy(&data->strs[pos], name->str, name->len); + pos += name->len; + memcpy(&data->strs[pos], pw->str, pw->len); + pos += pw->len; + memcpy(&data->strs[pos], gecos->str, gecos->len); + pos += gecos->len; + memcpy(&data->strs[pos], homedir->str, homedir->len); + pos += homedir->len; + memcpy(&data->strs[pos], shell->str, shell->len); + + MC_LOWER_BARRIER(rec); + + /* finally chain the rec in the hash table */ + sss_mmap_chain_in_rec(mcc, rec); + + return EOK; +} + +errno_t sss_mmap_cache_pw_invalidate(struct sss_mc_ctx **_mcc, + const struct sized_string *name) +{ + return sss_mmap_cache_invalidate(_mcc, name); +} + +errno_t sss_mmap_cache_pw_invalidate_uid(struct sss_mc_ctx **_mcc, uid_t uid) +{ + struct sss_mc_ctx *mcc; + struct sss_mc_rec *rec = NULL; + struct sss_mc_pwd_data *data; + uint32_t hash; + uint32_t slot; + char *uidstr; + errno_t ret; + + ret = sss_mmap_cache_validate_or_reinit(_mcc); + if (ret != EOK) { + return ret; + } + + mcc = *_mcc; + + uidstr = talloc_asprintf(NULL, "%ld", (long)uid); + if (!uidstr) { + return ENOMEM; + } + + hash = sss_mc_hash(mcc, uidstr, strlen(uidstr) + 1); + + slot = mcc->hash_table[hash]; + if (!MC_SLOT_WITHIN_BOUNDS(slot, mcc->dt_size)) { + ret = ENOENT; + goto done; + } + + while (slot != MC_INVALID_VAL) { + if (!MC_SLOT_WITHIN_BOUNDS(slot, mcc->dt_size)) { + DEBUG(SSSDBG_FATAL_FAILURE, "Corrupted memcache.\n"); + sss_mc_save_corrupted(mcc); + sss_mmap_cache_reset(mcc); + ret = ENOENT; + goto done; + } + + rec = MC_SLOT_TO_PTR(mcc->data_table, slot, struct sss_mc_rec); + data = (struct sss_mc_pwd_data *)(&rec->data); + + if (uid == data->uid) { + break; + } + + slot = sss_mc_next_slot_with_hash(rec, hash); + } + + if (slot == MC_INVALID_VAL) { + ret = ENOENT; + goto done; + } + + sss_mc_invalidate_rec(mcc, rec); + + ret = EOK; + +done: + talloc_zfree(uidstr); + return ret; +} + +/*************************************************************************** + * group map + ***************************************************************************/ + +int sss_mmap_cache_gr_store(struct sss_mc_ctx **_mcc, + const struct sized_string *name, + const struct sized_string *pw, + gid_t gid, size_t memnum, + const char *membuf, size_t memsize) +{ + struct sss_mc_ctx *mcc; + struct sss_mc_rec *rec; + struct sss_mc_grp_data *data; + struct sized_string gidkey; + char gidstr[11]; + size_t data_len; + size_t rec_len; + size_t pos; + int ret; + + ret = sss_mmap_cache_validate_or_reinit(_mcc); + if (ret != EOK) { + return ret; + } + + mcc = *_mcc; + + ret = snprintf(gidstr, 11, "%ld", (long)gid); + if (ret > 10) { + return EINVAL; + } + to_sized_string(&gidkey, gidstr); + + data_len = name->len + pw->len + memsize; + rec_len = sizeof(struct sss_mc_rec) + + sizeof(struct sss_mc_grp_data) + + data_len; + if (rec_len > mcc->dt_size) { + return ENOMEM; + } + + ret = sss_mc_get_record(_mcc, rec_len, name, &rec); + if (ret != EOK) { + return ret; + } + + data = (struct sss_mc_grp_data *)rec->data; + pos = 0; + + MC_RAISE_BARRIER(rec); + + /* header */ + sss_mmap_set_rec_header(mcc, rec, rec_len, mcc->valid_time_slot, + name->str, name->len, gidkey.str, gidkey.len); + + /* group struct */ + data->name = MC_PTR_DIFF(data->strs, data); + data->gid = gid; + data->members = memnum; + data->strs_len = data_len; + memcpy(&data->strs[pos], name->str, name->len); + pos += name->len; + memcpy(&data->strs[pos], pw->str, pw->len); + pos += pw->len; + memcpy(&data->strs[pos], membuf, memsize); + + MC_LOWER_BARRIER(rec); + + /* finally chain the rec in the hash table */ + sss_mmap_chain_in_rec(mcc, rec); + + return EOK; +} + +errno_t sss_mmap_cache_gr_invalidate(struct sss_mc_ctx **_mcc, + const struct sized_string *name) +{ + return sss_mmap_cache_invalidate(_mcc, name); +} + +errno_t sss_mmap_cache_gr_invalidate_gid(struct sss_mc_ctx **_mcc, gid_t gid) +{ + struct sss_mc_ctx *mcc; + struct sss_mc_rec *rec = NULL; + struct sss_mc_grp_data *data; + uint32_t hash; + uint32_t slot; + char *gidstr; + errno_t ret; + + ret = sss_mmap_cache_validate_or_reinit(_mcc); + if (ret != EOK) { + return ret; + } + + mcc = *_mcc; + + gidstr = talloc_asprintf(NULL, "%ld", (long)gid); + if (!gidstr) { + return ENOMEM; + } + + hash = sss_mc_hash(mcc, gidstr, strlen(gidstr) + 1); + + slot = mcc->hash_table[hash]; + if (!MC_SLOT_WITHIN_BOUNDS(slot, mcc->dt_size)) { + ret = ENOENT; + goto done; + } + + while (slot != MC_INVALID_VAL) { + if (!MC_SLOT_WITHIN_BOUNDS(slot, mcc->dt_size)) { + DEBUG(SSSDBG_FATAL_FAILURE, "Corrupted memcache.\n"); + sss_mc_save_corrupted(mcc); + sss_mmap_cache_reset(mcc); + ret = ENOENT; + goto done; + } + + rec = MC_SLOT_TO_PTR(mcc->data_table, slot, struct sss_mc_rec); + data = (struct sss_mc_grp_data *)(&rec->data); + + if (gid == data->gid) { + break; + } + + slot = sss_mc_next_slot_with_hash(rec, hash); + } + + if (slot == MC_INVALID_VAL) { + ret = ENOENT; + goto done; + } + + sss_mc_invalidate_rec(mcc, rec); + + ret = EOK; + +done: + talloc_zfree(gidstr); + return ret; +} + +errno_t sss_mmap_cache_initgr_store(struct sss_mc_ctx **_mcc, + const struct sized_string *name, + const struct sized_string *unique_name, + uint32_t num_groups, + const uint8_t *gids_buf) +{ + struct sss_mc_ctx *mcc; + struct sss_mc_rec *rec; + struct sss_mc_initgr_data *data; + size_t data_len; + size_t rec_len; + size_t pos; + int ret; + + ret = sss_mmap_cache_validate_or_reinit(_mcc); + if (ret != EOK) { + return ret; + } + + mcc = *_mcc; + + /* array of gids + name + unique_name */ + data_len = num_groups * sizeof(uint32_t) + name->len + unique_name->len; + rec_len = sizeof(struct sss_mc_rec) + sizeof(struct sss_mc_initgr_data) + + data_len; + if (rec_len > mcc->dt_size) { + return ENOMEM; + } + + /* use unique name for searching potential old records */ + ret = sss_mc_get_record(_mcc, rec_len, unique_name, &rec); + if (ret != EOK) { + return ret; + } + + data = (struct sss_mc_initgr_data *)rec->data; + pos = 0; + + MC_RAISE_BARRIER(rec); + + sss_mmap_set_rec_header(mcc, rec, rec_len, mcc->valid_time_slot, + name->str, name->len, + unique_name->str, unique_name->len); + + /* initgroups struct */ + data->strs_len = name->len + unique_name->len; + data->data_len = data_len; + data->num_groups = num_groups; + memcpy((char *)data->gids + pos, gids_buf, num_groups * sizeof(uint32_t)); + pos += num_groups * sizeof(uint32_t); + + memcpy((char *)data->gids + pos, unique_name->str, unique_name->len); + data->strs = data->unique_name = MC_PTR_DIFF((char *)data->gids + pos, data); + pos += unique_name->len; + + memcpy((char *)data->gids + pos, name->str, name->len); + data->name = MC_PTR_DIFF((char *)data->gids + pos, data); + + MC_LOWER_BARRIER(rec); + + /* finally chain the rec in the hash table */ + sss_mmap_chain_in_rec(mcc, rec); + + return EOK; +} + +errno_t sss_mmap_cache_initgr_invalidate(struct sss_mc_ctx **_mcc, + const struct sized_string *name) +{ + return sss_mmap_cache_invalidate(_mcc, name); +} + +errno_t sss_mmap_cache_sid_store(struct sss_mc_ctx **_mcc, + const struct sized_string *sid, + uint32_t id, + uint32_t type, + bool explicit_lookup) +{ + struct sss_mc_ctx *mcc; + struct sss_mc_rec *rec; + struct sss_mc_sid_data *data; + char idkey[16]; + size_t rec_len; + int ret; + + ret = sss_mmap_cache_validate_or_reinit(_mcc); + if (ret != EOK) { + return ret; + } + + mcc = *_mcc; + + ret = snprintf(idkey, sizeof(idkey), "%d-%ld", + (type == SSS_ID_TYPE_GID) ? SSS_ID_TYPE_GID : SSS_ID_TYPE_UID, + (long)id); + if (ret > (sizeof(idkey) - 1)) { + return EINVAL; + } + + rec_len = sizeof(struct sss_mc_rec) + + sizeof(struct sss_mc_sid_data) + + sid->len; + + ret = sss_mc_get_record(_mcc, rec_len, sid, &rec); + if (ret != EOK) { + return ret; + } + + data = (struct sss_mc_sid_data *)rec->data; + MC_RAISE_BARRIER(rec); + + sss_mmap_set_rec_header(mcc, rec, rec_len, mcc->valid_time_slot, + sid->str, sid->len, idkey, strlen(idkey) + 1); + + data->name = MC_PTR_DIFF(data->sid, data); + data->type = type; + data->id = id; + data->populated_by = (explicit_lookup ? 1 : 0); + data->sid_len = sid->len; + memcpy(data->sid, sid->str, sid->len); + + MC_LOWER_BARRIER(rec); + sss_mmap_chain_in_rec(mcc, rec); + + return EOK; +} + +/*************************************************************************** + * initialization + ***************************************************************************/ + +/* Copy of sss_mc_set_recycled is present in the src/tools/tools_mc_util.c. + * If you modify this function, you should modify the duplicated function + * too. */ +static errno_t sss_mc_set_recycled(int fd) +{ + uint32_t w = SSS_MC_HEADER_RECYCLED; + off_t offset; + off_t pos; + ssize_t written; + + offset = offsetof(struct sss_mc_header, status); + + pos = lseek(fd, offset, SEEK_SET); + if (pos == -1) { + /* What do we do now? */ + return errno; + } + + errno = 0; + written = sss_atomic_write_s(fd, (uint8_t *)&w, sizeof(w)); + if (written == -1) { + return errno; + } + + if (written != sizeof(w)) { + /* Write error */ + return EIO; + } + + return EOK; +} + +static void sss_mc_destroy_file(const char *filename) +{ + const useconds_t t = 50000; + const int retries = 3; + int ofd; + int ret; + + ofd = open(filename, O_RDWR); + if (ofd != -1) { + ret = sss_br_lock_file(ofd, 0, 1, retries, t); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to lock file %s.\n", filename); + } + ret = sss_mc_set_recycled(ofd); + if (ret) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to mark mmap file %s as" + " recycled: %d (%s)\n", + filename, ret, strerror(ret)); + } + close(ofd); + } else if (errno != ENOENT) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to open old memory cache file %s: %d (%s)\n", + filename, ret, strerror(ret)); + } + + errno = 0; + ret = unlink(filename); + if (ret == -1 && errno != ENOENT) { + ret = errno; + DEBUG(SSSDBG_TRACE_FUNC, "Failed to delete mmap file %s: %d (%s)\n", + filename, ret, strerror(ret)); + } +} + +static errno_t sss_mc_create_file(struct sss_mc_ctx *mc_ctx) +{ + const useconds_t t = 50000; + const int retries = 3; + mode_t old_mask; + int ret, uret; + + /* temporarily relax umask as we need the file to be readable + * by everyone for now */ + old_mask = umask(0022); + + errno = 0; + mc_ctx->fd = open(mc_ctx->file, O_CREAT | O_EXCL | O_RDWR, 0644); + umask(old_mask); + if (mc_ctx->fd == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to open mmap file %s: %d(%s)\n", + mc_ctx->file, ret, strerror(ret)); + return ret; + } + + /* Make sure that the memory cache files are chowned to sssd.sssd even + * if the nss responder runs as root. This is because the specfile + * has the ownership recorded as sssd.sssd + */ + ret = fchown(mc_ctx->fd, mc_ctx->uid, mc_ctx->gid); + if (ret != 0) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to chown mmap file %s: %d(%s)\n", + mc_ctx->file, ret, strerror(ret)); + return ret; + } + + ret = fchmod(mc_ctx->fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to chmod mmap file %s: %d(%s)\n", + mc_ctx->file, ret, strerror(ret)); + return ret; + } + + ret = sss_br_lock_file(mc_ctx->fd, 0, 1, retries, t); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to lock file %s.\n", mc_ctx->file); + close(mc_ctx->fd); + mc_ctx->fd = -1; + + /* Report on unlink failures but don't overwrite the errno + * from sss_br_lock_file + */ + errno = 0; + uret = unlink(mc_ctx->file); + if (uret == -1) { + uret = errno; + DEBUG(SSSDBG_TRACE_FUNC, "Failed to rm mmap file %s: %d(%s)\n", + mc_ctx->file, uret, strerror(uret)); + } + + return ret; + } + + return ret; +} + +static void sss_mc_header_update(struct sss_mc_ctx *mc_ctx, int status) +{ + struct sss_mc_header *h; + + /* update header using barriers */ + h = (struct sss_mc_header *)mc_ctx->mmap_base; + MC_RAISE_BARRIER(h); + if (status == SSS_MC_HEADER_ALIVE) { + /* no reason to update anything else if the file is recycled or + * right before reset */ + h->hash_table = MC_PTR_DIFF(mc_ctx->hash_table, mc_ctx->mmap_base); + h->free_table = MC_PTR_DIFF(mc_ctx->free_table, mc_ctx->mmap_base); + h->data_table = MC_PTR_DIFF(mc_ctx->data_table, mc_ctx->mmap_base); + h->ht_size = mc_ctx->ht_size; + h->ft_size = mc_ctx->ft_size; + h->dt_size = mc_ctx->dt_size; + h->major_vno = SSS_MC_MAJOR_VNO; + h->minor_vno = SSS_MC_MINOR_VNO; + h->seed = mc_ctx->seed; + h->reserved = 0; + } + h->status = status; + MC_LOWER_BARRIER(h); +} + +static int mc_ctx_destructor(struct sss_mc_ctx *mc_ctx) +{ + int ret; + + /* Print debug message to logs if munmap() or close() + * fail but always return 0 */ + + if (mc_ctx->mmap_base != NULL) { + ret = munmap(mc_ctx->mmap_base, mc_ctx->mmap_size); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to unmap old memory cache file." + "[%d]: %s\n", ret, strerror(ret)); + } + } + + if (mc_ctx->fd != -1) { + ret = close(mc_ctx->fd); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to close old memory cache file." + "[%d]: %s\n", ret, strerror(ret)); + } + } + + return 0; +} + +#define POSIX_FALLOCATE_ATTEMPTS 3 + +errno_t sss_mmap_cache_init(TALLOC_CTX *mem_ctx, const char *name, + uid_t uid, gid_t gid, + enum sss_mc_type type, size_t n_elem, + time_t timeout, struct sss_mc_ctx **mcc) +{ + /* sss_mc_rec alone occupies whole slot, + * so each entry takes 2 slots at the very least + */ + static const int PAYLOAD_FACTOR = 2; + + struct sss_mc_ctx *mc_ctx = NULL; + int ret, dret; + char *filename; + + filename = talloc_asprintf(mem_ctx, "%s/%s", SSS_NSS_MCACHE_DIR, name); + if (!filename) { + return ENOMEM; + } + /* + * First of all mark the current file as recycled + * and unlink so active clients will abandon its use ASAP + */ + sss_mc_destroy_file(filename); + + if ((timeout == 0) || (n_elem == 0)) { + DEBUG(SSSDBG_IMPORTANT_INFO, + "Fast '%s' mmap cache is explicitly DISABLED\n", + mc_type_to_str(type)); + *mcc = NULL; + return EOK; + } + DEBUG(SSSDBG_CONF_SETTINGS, + "Fast '%s' mmap cache: memcache_timeout = %"SPRItime", slots = %zu\n", + mc_type_to_str(type), timeout, n_elem); + + mc_ctx = talloc_zero(mem_ctx, struct sss_mc_ctx); + if (!mc_ctx) { + talloc_free(filename); + return ENOMEM; + } + mc_ctx->fd = -1; + talloc_set_destructor(mc_ctx, mc_ctx_destructor); + + mc_ctx->name = talloc_strdup(mc_ctx, name); + if (!mc_ctx->name) { + ret = ENOMEM; + goto done; + } + + mc_ctx->uid = uid; + mc_ctx->gid = gid; + + mc_ctx->type = type; + + mc_ctx->valid_time_slot = timeout; + + mc_ctx->file = talloc_steal(mc_ctx, filename); + + /* elements must always be multiple of 8 to make things easier to handle, + * so we increase by the necessary amount if they are not a multiple */ + /* We can use MC_ALIGN64 for this */ + n_elem = MC_ALIGN64(n_elem); + + /* hash table is double the size because it will store both forward and + * reverse keys (name/uid, name/gid, ..) */ + mc_ctx->ht_size = MC_HT_SIZE(2 * n_elem / PAYLOAD_FACTOR); + mc_ctx->dt_size = n_elem * MC_SLOT_SIZE; + mc_ctx->ft_size = n_elem / 8; /* 1 bit per slot */ + mc_ctx->mmap_size = MC_HEADER_SIZE + + MC_ALIGN64(mc_ctx->dt_size) + + MC_ALIGN64(mc_ctx->ft_size) + + MC_ALIGN64(mc_ctx->ht_size); + + + ret = sss_mc_create_file(mc_ctx); + if (ret) { + goto done; + } + + /* Attempt allocation several times, in case of EINTR */ + for (int i = 0; i < POSIX_FALLOCATE_ATTEMPTS; i++) { + ret = posix_fallocate(mc_ctx->fd, 0, mc_ctx->mmap_size); + if (ret != EINTR) + break; + } + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to allocate file %s: %d(%s)\n", + mc_ctx->file, ret, strerror(ret)); + goto done; + } + + mc_ctx->mmap_base = mmap(NULL, mc_ctx->mmap_size, + PROT_READ | PROT_WRITE, + MAP_SHARED, mc_ctx->fd, 0); + if (mc_ctx->mmap_base == MAP_FAILED) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to mmap file %s(%zu): %d(%s)\n", + mc_ctx->file, mc_ctx->mmap_size, + ret, strerror(ret)); + goto done; + } + + mc_ctx->data_table = MC_PTR_ADD(mc_ctx->mmap_base, MC_HEADER_SIZE); + mc_ctx->free_table = MC_PTR_ADD(mc_ctx->data_table, + MC_ALIGN64(mc_ctx->dt_size)); + mc_ctx->hash_table = MC_PTR_ADD(mc_ctx->free_table, + MC_ALIGN64(mc_ctx->ft_size)); + + memset(mc_ctx->data_table, 0xff, mc_ctx->dt_size); + memset(mc_ctx->free_table, 0x00, mc_ctx->ft_size); + memset(mc_ctx->hash_table, 0xff, mc_ctx->ht_size); + + /* generate a pseudo-random seed. + * Needed to fend off dictionary based collision attacks */ + ret = sss_generate_csprng_buffer((uint8_t *)&mc_ctx->seed, sizeof(mc_ctx->seed)); + if (ret != EOK) { + goto done; + } + + sss_mc_header_update(mc_ctx, SSS_MC_HEADER_ALIVE); + + ret = EOK; + +done: + if (ret) { + /* Closing the file descriptor and unmapping the file + * from memory is done in the mc_ctx_destructor. */ + if (mc_ctx && mc_ctx->file && mc_ctx->fd != -1) { + dret = unlink(mc_ctx->file); + if (dret == -1) { + dret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to rm mmap file %s: %d(%s)\n", mc_ctx->file, + dret, strerror(dret)); + } + } + + talloc_free(mc_ctx); + } else { + *mcc = mc_ctx; + } + return ret; +} + +errno_t sss_mmap_cache_reinit(TALLOC_CTX *mem_ctx, + uid_t uid, gid_t gid, + size_t n_elem, + time_t timeout, struct sss_mc_ctx **mc_ctx) +{ + errno_t ret; + TALLOC_CTX* tmp_ctx = NULL; + char *name; + enum sss_mc_type type; + + if (mc_ctx == NULL || (*mc_ctx) == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to re-init uninitialized memory cache.\n"); + return EINVAL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory.\n"); + return ENOMEM; + } + + name = talloc_strdup(tmp_ctx, (*mc_ctx)->name); + if (name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory.\n"); + ret = ENOMEM; + goto done; + } + + type = (*mc_ctx)->type; + + if (n_elem == (size_t)-1) { + n_elem = (*mc_ctx)->ft_size * 8; + } + + if (timeout == (time_t)-1) { + timeout = (*mc_ctx)->valid_time_slot; + } + + if (uid == (uid_t)-1) { + uid = (*mc_ctx)->uid; + } + + if (gid == (gid_t)-1) { + gid = (*mc_ctx)->gid; + } + + talloc_free(*mc_ctx); + + /* make sure we do not leave a potentially freed pointer around */ + *mc_ctx = NULL; + + ret = sss_mmap_cache_init(mem_ctx, + name, + uid, gid, + type, + n_elem, + timeout, + mc_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to re-initialize mmap cache.\n"); + goto done; + } + +done: + talloc_free(tmp_ctx); + return ret; +} + +/* Erase all contents of the mmap cache. This will bring the cache + * to the same state as if it was just initialized. */ +void sss_mmap_cache_reset(struct sss_mc_ctx *mc_ctx) +{ + if (mc_ctx == NULL) { + DEBUG(SSSDBG_TRACE_FUNC, + "Fastcache not initialized. Nothing to do.\n"); + return; + } + + sss_mc_header_update(mc_ctx, SSS_MC_HEADER_UNINIT); + + /* Reset the mmapped area */ + memset(mc_ctx->data_table, 0xff, mc_ctx->dt_size); + memset(mc_ctx->free_table, 0x00, mc_ctx->ft_size); + memset(mc_ctx->hash_table, 0xff, mc_ctx->ht_size); + + sss_mc_header_update(mc_ctx, SSS_MC_HEADER_ALIVE); +} diff --git a/src/responder/nss/nsssrv_mmap_cache.h b/src/responder/nss/nsssrv_mmap_cache.h new file mode 100644 index 0000000..28ee5ad --- /dev/null +++ b/src/responder/nss/nsssrv_mmap_cache.h @@ -0,0 +1,86 @@ +/* + SSSD + + NSS Responder - Mmap Cache + + Copyright (C) Simo Sorce <ssorce@redhat.com> 2011 + + 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/>. +*/ + +#ifndef _NSSSRV_MMAP_CACHE_H_ +#define _NSSSRV_MMAP_CACHE_H_ + +struct sss_mc_ctx; + +enum sss_mc_type { + SSS_MC_NONE = 0, + SSS_MC_PASSWD, + SSS_MC_GROUP, + SSS_MC_INITGROUPS, + SSS_MC_SID, +}; + +errno_t sss_mmap_cache_init(TALLOC_CTX *mem_ctx, const char *name, + uid_t uid, gid_t gid, + enum sss_mc_type type, size_t n_elem, + time_t valid_time, struct sss_mc_ctx **mcc); + +errno_t sss_mmap_cache_pw_store(struct sss_mc_ctx **_mcc, + const struct sized_string *name, + const struct sized_string *pw, + uid_t uid, gid_t gid, + const struct sized_string *gecos, + const struct sized_string *homedir, + const struct sized_string *shell); + +errno_t sss_mmap_cache_gr_store(struct sss_mc_ctx **_mcc, + const struct sized_string *name, + const struct sized_string *pw, + gid_t gid, size_t memnum, + const char *membuf, size_t memsize); + +errno_t sss_mmap_cache_initgr_store(struct sss_mc_ctx **_mcc, + const struct sized_string *name, + const struct sized_string *unique_name, + uint32_t num_groups, + const uint8_t *gids_buf); + +errno_t sss_mmap_cache_sid_store(struct sss_mc_ctx **_mcc, + const struct sized_string *sid, + uint32_t id, + uint32_t type, /* enum sss_id_type*/ + bool explicit_lookup); /* false ~ by_id(), true ~ by_uid/gid() */ + +errno_t sss_mmap_cache_pw_invalidate(struct sss_mc_ctx **_mcc, + const struct sized_string *name); + +errno_t sss_mmap_cache_pw_invalidate_uid(struct sss_mc_ctx **_mcc, uid_t uid); + +errno_t sss_mmap_cache_gr_invalidate(struct sss_mc_ctx **_mcc, + const struct sized_string *name); + +errno_t sss_mmap_cache_gr_invalidate_gid(struct sss_mc_ctx **_mcc, gid_t gid); + +errno_t sss_mmap_cache_initgr_invalidate(struct sss_mc_ctx **_mcc, + const struct sized_string *name); + +errno_t sss_mmap_cache_reinit(TALLOC_CTX *mem_ctx, + uid_t uid, gid_t gid, + size_t n_elem, + time_t timeout, struct sss_mc_ctx **mc_ctx); + +void sss_mmap_cache_reset(struct sss_mc_ctx *mc_ctx); + +#endif /* _NSSSRV_MMAP_CACHE_H_ */ diff --git a/src/responder/pac/pacsrv.c b/src/responder/pac/pacsrv.c new file mode 100644 index 0000000..8d3db9d --- /dev/null +++ b/src/responder/pac/pacsrv.c @@ -0,0 +1,230 @@ +/* + SSSD + + PAC Responder + + Copyright (C) Sumit Bose <sbose@redhat.com> 2011 + + 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 <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <string.h> +#include <sys/time.h> +#include <errno.h> +#include <popt.h> +#include <dbus/dbus.h> + +#include "util/util.h" +#include "responder/pac/pacsrv.h" +#include "db/sysdb.h" +#include "confdb/confdb.h" +#include "responder/common/responder_packet.h" +#include "responder/common/responder.h" +#include "providers/data_provider.h" +#include "util/util_sss_idmap.h" +#include "sss_iface/sss_iface_async.h" + +#define SSS_PAC_PIPE_NAME "pac" +#define DEFAULT_PAC_FD_LIMIT 8192 +#define DEFAULT_ALLOWED_UIDS "0" + +int pac_process_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct confdb_ctx *cdb) +{ + struct resp_ctx *rctx; + struct sss_cmd_table *pac_cmds; + struct pac_ctx *pac_ctx; + int ret; + enum idmap_error_code err; + int fd_limit; + char *uid_str; + + pac_cmds = get_pac_cmds(); + + ret = sss_process_init(mem_ctx, ev, cdb, + pac_cmds, + SSS_PAC_SOCKET_NAME, -1, NULL, -1, + CONFDB_PAC_CONF_ENTRY, + SSS_BUS_PAC, PAC_SBUS_SERVICE_NAME, + sss_connection_setup, + &rctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "sss_process_init() failed\n"); + return ret; + } + + pac_ctx = talloc_zero(rctx, struct pac_ctx); + if (!pac_ctx) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error initializing pac_ctx\n"); + ret = ENOMEM; + goto fail; + } + + pac_ctx->rctx = rctx; + pac_ctx->rctx->pvt_ctx = pac_ctx; + + ret = confdb_get_string(pac_ctx->rctx->cdb, pac_ctx->rctx, + CONFDB_PAC_CONF_ENTRY, CONFDB_SERVICE_ALLOWED_UIDS, + DEFAULT_ALLOWED_UIDS, &uid_str); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to get allowed UIDs.\n"); + goto fail; + } + + ret = csv_string_to_uid_array(pac_ctx->rctx, uid_str, + &pac_ctx->rctx->allowed_uids_count, + &pac_ctx->rctx->allowed_uids); + talloc_free(uid_str); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to set allowed UIDs.\n"); + goto fail; + } + + err = sss_idmap_init(sss_idmap_talloc, pac_ctx, sss_idmap_talloc_free, + &pac_ctx->idmap_ctx); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_FATAL_FAILURE, "sss_idmap_init failed.\n"); + ret = EFAULT; + goto fail; + } + + /* Set up file descriptor limits */ + ret = confdb_get_int(pac_ctx->rctx->cdb, + CONFDB_PAC_CONF_ENTRY, + CONFDB_SERVICE_FD_LIMIT, + DEFAULT_PAC_FD_LIMIT, + &fd_limit); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to set up file descriptor limit\n"); + goto fail; + } + responder_set_fd_limit(fd_limit); + + ret = confdb_get_int(pac_ctx->rctx->cdb, CONFDB_PAC_CONF_ENTRY, + CONFDB_PAC_LIFETIME, 300, + &pac_ctx->pac_lifetime); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to setup pac lifetime timeout [%s].\n", + CONFDB_PAC_LIFETIME); + goto fail; + } + + ret = get_pac_check_config(pac_ctx->rctx->cdb, &pac_ctx->pac_check_opts); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to get pac_check"); + goto fail; + } + + ret = schedule_get_domains_task(rctx, rctx->ev, rctx, NULL, NULL, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "schedule_get_domains_tasks failed.\n"); + goto fail; + } + + /* The responder is initialized. Now tell it to the monitor. */ + ret = sss_monitor_service_init(rctx, rctx->ev, SSS_BUS_PAC, + PAC_SBUS_SERVICE_NAME, + PAC_SBUS_SERVICE_VERSION, + MT_SVC_SERVICE, + &rctx->last_request_time, &rctx->mon_conn); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error setting up message bus\n"); + goto fail; + } + + ret = sss_resp_register_service_iface(rctx); + if (ret != EOK) { + goto fail; + } + + DEBUG(SSSDBG_TRACE_FUNC, "PAC Initialization complete\n"); + + return EOK; + +fail: + talloc_free(rctx); + return ret; +} + +int main(int argc, const char *argv[]) +{ + int opt; + poptContext pc; + char *opt_logger = NULL; + struct main_context *main_ctx; + int ret; + uid_t uid = 0; + gid_t gid = 0; + + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_MAIN_OPTS + SSSD_LOGGER_OPTS + SSSD_SERVER_OPTS(uid, gid) + SSSD_RESPONDER_OPTS + POPT_TABLEEND + }; + + /* Set debug level to invalid value so we can decide if -d 0 was used. */ + debug_level = SSSDBG_INVALID; + + umask(DFL_RSP_UMASK); + + pc = poptGetContext(argv[0], argc, argv, long_options, 0); + while((opt = poptGetNextOpt(pc)) != -1) { + switch(opt) { + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + return 1; + } + } + + poptFreeContext(pc); + + /* set up things like debug, signals, daemonization, etc. */ + debug_log_file = "sssd_pac"; + DEBUG_INIT(debug_level, opt_logger); + + ret = server_setup("pac", true, 0, uid, gid, + CONFDB_PAC_CONF_ENTRY, &main_ctx, true); + if (ret != EOK) return 2; + + ret = die_if_parent_died(); + if (ret != EOK) { + /* This is not fatal, don't return */ + DEBUG(SSSDBG_OP_FAILURE, "Could not set up to exit when parent process does\n"); + } + + ret = pac_process_init(main_ctx, + main_ctx->event_ctx, + main_ctx->confdb_ctx); + if (ret != EOK) return 3; + + /* loop on main */ + server_loop(main_ctx); + + return 0; +} diff --git a/src/responder/pac/pacsrv.h b/src/responder/pac/pacsrv.h new file mode 100644 index 0000000..f3b8e45 --- /dev/null +++ b/src/responder/pac/pacsrv.h @@ -0,0 +1,42 @@ +/* + SSSD + + PAC Responder, header file + + Copyright (C) Sumit Bose <sbose@redhat.com> 2011 + + 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/>. +*/ + +#ifndef __PACSRV_H__ +#define __PACSRV_H__ + +#include "config.h" + +#include "responder/common/responder_packet.h" +#include "responder/common/responder.h" +#include "lib/idmap/sss_idmap.h" + +struct pac_ctx { + struct resp_ctx *rctx; + struct sss_idmap_ctx *idmap_ctx; + struct dom_sid *my_dom_sid; + struct local_mapping_ranges *range_map; + int pac_lifetime; + uint32_t pac_check_opts; +}; + +struct sss_cmd_table *get_pac_cmds(void); + +#endif /* __PACSRV_H__ */ diff --git a/src/responder/pac/pacsrv_cmd.c b/src/responder/pac/pacsrv_cmd.c new file mode 100644 index 0000000..abfc2c9 --- /dev/null +++ b/src/responder/pac/pacsrv_cmd.c @@ -0,0 +1,311 @@ +/* + SSSD + + PAC Responder + + Copyright (C) Sumit Bose <sbose@redhat.com> 2012, 2016 + Jan Zeleny <jzeleny@redhat.com> 2012 + + 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 "util/util.h" +#include "responder/pac/pacsrv.h" +#include "responder/common/cache_req/cache_req.h" +#include "confdb/confdb.h" + +#include "providers/ad/ad_pac.h" + +static errno_t pac_cmd_done(struct cli_ctx *cctx, int cmd_ret) +{ + struct cli_protocol *pctx; + int ret; + + if (cmd_ret == EAGAIN) { + /* async processing, just return here */ + return EOK; + } + + pctx = talloc_get_type(cctx->protocol_ctx, struct cli_protocol); + + ret = sss_packet_new(pctx->creq, 0, sss_packet_get_cmd(pctx->creq->in), + &pctx->creq->out); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_packet_new failed [%d][%s].\n", + ret, strerror(ret)); + return ret; + } + + sss_packet_set_error(pctx->creq->out, cmd_ret); + if (cmd_ret != 0) { + DEBUG(SSSDBG_TRACE_ALL, "Sending error [%d][%s].\n", cmd_ret, + sss_strerror(cmd_ret)); + } + + sss_cmd_done(cctx, NULL); + + return EOK; +} + +struct pac_req_ctx { + struct cli_ctx *cctx; + struct pac_ctx *pac_ctx; + const char *domain_name; + struct sss_domain_info *dom; + + uint8_t *blob; + size_t blen; + + struct PAC_LOGON_INFO *logon_info; + struct PAC_UPN_DNS_INFO *upn_dns_info; + + char *user_sid_str; + char *user_dom_sid_str; +}; + +static errno_t pac_resolve_user_sid_next(struct pac_req_ctx *pr_ctx); +static void pac_resolve_user_sid_done(struct tevent_req *req); +static void pac_get_domains_done(struct tevent_req *req); + +static errno_t pac_add_pac_user(struct cli_ctx *cctx) +{ + int ret; + uint8_t *body; + size_t blen; + struct pac_req_ctx *pr_ctx; + struct tevent_req *req; + enum idmap_error_code err; + struct cli_protocol *pctx; + + pctx = talloc_get_type(cctx->protocol_ctx, struct cli_protocol); + + sss_packet_get_body(pctx->creq->in, &body, &blen); + + pr_ctx = talloc_zero(cctx, struct pac_req_ctx); + if (pr_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + pr_ctx->cctx = cctx; + pr_ctx->blob = body; + pr_ctx->blen = blen; + + pr_ctx->pac_ctx = talloc_get_type(cctx->rctx->pvt_ctx, struct pac_ctx); + if (pr_ctx->pac_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Cannot find pac responder context.\n"); + return EINVAL; + } + + ret = ad_get_data_from_pac(pr_ctx, pr_ctx->pac_ctx->pac_check_opts, + body, blen, + &pr_ctx->logon_info, &pr_ctx->upn_dns_info); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "ad_get_data_from_pac failed.\n"); + goto done; + } + + pr_ctx->domain_name = pr_ctx->logon_info->info3.base.logon_domain.string; + if (pr_ctx->domain_name == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "No domain name in PAC\n"); + ret = EINVAL; + goto done; + } + + err = sss_idmap_smb_sid_to_sid(pr_ctx->pac_ctx->idmap_ctx, + pr_ctx->logon_info->info3.base.domain_sid, + &pr_ctx->user_dom_sid_str); + if (err != IDMAP_SUCCESS) { + DEBUG(SSSDBG_OP_FAILURE, "sss_idmap_smb_sid_to_sid failed.\n"); + ret = EFAULT; + goto done; + } + + talloc_steal(pr_ctx, pr_ctx->user_dom_sid_str); + + pr_ctx->user_sid_str = talloc_asprintf(pr_ctx, "%s-%"PRIu32, + pr_ctx->user_dom_sid_str, + pr_ctx->logon_info->info3.base.rid); + if (pr_ctx->user_sid_str == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = responder_get_domain_by_id(cctx->rctx, pr_ctx->user_dom_sid_str, + &pr_ctx->dom); + if (ret == EAGAIN || ret == ENOENT) { + req = sss_dp_get_domains_send(cctx->rctx, cctx->rctx, true, + pr_ctx->domain_name); + if (req == NULL) { + ret = ENOMEM; + } else { + tevent_req_set_callback(req, pac_get_domains_done, pr_ctx); + ret = EAGAIN; + } + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "responder_get_domain_by_id failed.\n"); + goto done; + } + + ret = pac_resolve_user_sid_next(pr_ctx); + +done: + if (ret != EAGAIN) { + talloc_free(pr_ctx); + } + return pac_cmd_done(cctx, ret); +} + +static void pac_get_domains_done(struct tevent_req *req) +{ + struct pac_req_ctx *pr_ctx = tevent_req_callback_data(req, + struct pac_req_ctx); + struct cli_ctx *cctx = pr_ctx->cctx; + int ret; + + ret = sss_dp_get_domains_recv(req); + talloc_free(req); + if (ret != EOK) { + goto done; + } + + ret = responder_get_domain_by_id(cctx->rctx, pr_ctx->user_dom_sid_str, + &pr_ctx->dom); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Corresponding domain [%s] has not been " + "found\n", pr_ctx->user_dom_sid_str); + ret = ENOENT; + goto done; + } + + ret = pac_resolve_user_sid_next(pr_ctx); + +done: + if (ret != EAGAIN) { + talloc_free(pr_ctx); + } + pac_cmd_done(cctx, ret); +} + +static errno_t pac_resolve_user_sid_next(struct pac_req_ctx *pr_ctx) +{ + int ret; + struct tevent_req *req; + const char *pw_attrs[] = SYSDB_PW_ATTRS; + + + req = cache_req_object_by_sid_send(pr_ctx, pr_ctx->cctx->ev, + pr_ctx->cctx->rctx, + pr_ctx->pac_ctx->rctx->ncache, + 0, pr_ctx->dom->name, + pr_ctx->user_sid_str, + pw_attrs); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "cache_req_object_by_sid_send failed.\n"); + return ENOMEM; + } + + tevent_req_set_callback(req, pac_resolve_user_sid_done, pr_ctx); + + ret = EAGAIN; + + + return ret; +} + +static void pac_resolve_user_sid_done(struct tevent_req *req) +{ + struct pac_req_ctx *pr_ctx = tevent_req_callback_data(req, + struct pac_req_ctx); + struct cli_ctx *cctx = pr_ctx->cctx; + errno_t ret; + struct cache_req_result *result; + struct sysdb_attrs *user_attrs; + + ret = cache_req_object_by_sid_recv(pr_ctx, req, &result); + talloc_zfree(req); + + if (ret != EOK) { + talloc_free(pr_ctx); + pac_cmd_done(cctx, ret); + return; + } + + ret = check_upn_and_sid_from_user_and_pac(result->msgs[0], + pr_ctx->pac_ctx->idmap_ctx, + pr_ctx->upn_dns_info, + pr_ctx->pac_ctx->pac_check_opts); + if (ret != EOK) { + talloc_free(pr_ctx); + pac_cmd_done(cctx, ret); + return; + } + + user_attrs = sysdb_new_attrs(pr_ctx); + if (user_attrs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_new_attrs failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_add_mem(user_attrs, SYSDB_PAC_BLOB, pr_ctx->blob, + pr_ctx->blen); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_add_mem failed"); + goto done; + } + + ret = sysdb_attrs_add_time_t(user_attrs, SYSDB_PAC_BLOB_EXPIRE, + (time(NULL) + pr_ctx->pac_ctx->pac_lifetime)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_attrs_add_time_t failed"); + goto done; + } + + ret = sysdb_set_entry_attr(result->domain->sysdb, + result->msgs[0]->dn, user_attrs, + SYSDB_MOD_REP); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_set_entry_attr failed.\n"); + goto done; + } + +done: + talloc_free(pr_ctx); + pac_cmd_done(cctx, ret); + return; +} + + +struct cli_protocol_version *register_cli_protocol_version(void) +{ + static struct cli_protocol_version pac_cli_protocol_version[] = { + {1, "2011-04-12", "initial version"}, + {0, NULL, NULL} + }; + + return pac_cli_protocol_version; +} + +static struct sss_cmd_table pac_cmds[] = { + {SSS_GET_VERSION, sss_cmd_get_version}, + {SSS_PAC_ADD_PAC_USER, pac_add_pac_user}, + {SSS_CLI_NULL, NULL} +}; + +struct sss_cmd_table *get_pac_cmds(void) { + return pac_cmds; +} diff --git a/src/responder/pam/pam_helpers.c b/src/responder/pam/pam_helpers.c new file mode 100644 index 0000000..d0a79c5 --- /dev/null +++ b/src/responder/pam/pam_helpers.c @@ -0,0 +1,162 @@ +/* + SSSD + + Authors: + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2011 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 "src/responder/pam/pam_helpers.h" + +struct pam_initgr_table_ctx { + hash_table_t *id_table; + char *name; +}; + +static void pam_initgr_cache_remove(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *pvt); + +errno_t pam_initgr_cache_set(struct tevent_context *ev, + hash_table_t *id_table, + char *name, + long timeout) +{ + errno_t ret; + hash_key_t key; + hash_value_t val; + int hret; + struct tevent_timer *te; + struct timeval tv; + struct pam_initgr_table_ctx *table_ctx; + + ret = pam_initgr_check_timeout(id_table, name); + if (ret == EOK) { + /* user is already in the cache */ + goto done; + } + + table_ctx = talloc_zero(id_table, struct pam_initgr_table_ctx); + if (!table_ctx) return ENOMEM; + + table_ctx->id_table = id_table; + table_ctx->name = talloc_strdup(table_ctx, name); + if (!table_ctx->name) { + ret = ENOMEM; + goto done; + } + + key.type = HASH_KEY_STRING; + key.str = name; + + /* The value isn't relevant, since we're using + * a timer to remove the entry. + */ + val.type = HASH_VALUE_UNDEF; + + hret = hash_enter(id_table, &key, &val); + if (hret != HASH_SUCCESS) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not update initgr cache for [%s]: [%s]\n", + name, hash_error_string(hret)); + ret = EIO; + goto done; + } else { + DEBUG(SSSDBG_TRACE_INTERNAL, + "[%s] added to PAM initgroup cache\n", + name); + } + + /* Create a timer event to remove the entry from the cache */ + tv = tevent_timeval_current_ofs(timeout, 0); + te = tevent_add_timer(ev, table_ctx, tv, + pam_initgr_cache_remove, + table_ctx); + if (!te) { + ret = ENOMEM; + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(table_ctx); + } + return ret; +} + +static void pam_initgr_cache_remove(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *pvt) +{ + int hret; + hash_key_t key; + + struct pam_initgr_table_ctx *table_ctx = + talloc_get_type(pvt, struct pam_initgr_table_ctx); + + key.type = HASH_KEY_STRING; + key.str = table_ctx->name; + + hret = hash_delete(table_ctx->id_table, &key); + if (hret != HASH_SUCCESS + && hret != HASH_ERROR_KEY_NOT_FOUND) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Could not clear [%s] from initgr cache: [%s]\n", + table_ctx->name, + hash_error_string(hret)); + } else { + DEBUG(SSSDBG_TRACE_INTERNAL, + "[%s] removed from PAM initgroup cache\n", + table_ctx->name); + } + + talloc_free(table_ctx); +} + +errno_t pam_initgr_check_timeout(hash_table_t *id_table, + char *name) +{ + hash_key_t key; + hash_value_t val; + int hret; + + key.type = HASH_KEY_STRING; + key.str = name; + + hret = hash_lookup(id_table, &key, &val); + if (hret != HASH_SUCCESS + && hret != HASH_ERROR_KEY_NOT_FOUND) { + DEBUG(SSSDBG_TRACE_ALL, "Error searching user [%s] in PAM cache.\n", + name); + return EIO; + } else if (hret == HASH_ERROR_KEY_NOT_FOUND) { + DEBUG(SSSDBG_TRACE_ALL, "User [%s] not found in PAM cache.\n", name); + return ENOENT; + } + + /* If there's a value here, then the cache + * entry is still valid. + */ + DEBUG(SSSDBG_TRACE_INTERNAL, "User [%s] found in PAM cache.\n", name); + return EOK; +} + diff --git a/src/responder/pam/pam_helpers.h b/src/responder/pam/pam_helpers.h new file mode 100644 index 0000000..23fd308 --- /dev/null +++ b/src/responder/pam/pam_helpers.h @@ -0,0 +1,42 @@ +/* + SSSD + + Authors: + Stephen Gallagher <sgallagh@redhat.com> + + Copyright (C) 2011 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/>. +*/ + +#ifndef PAM_HELPERS_H_ +#define PAM_HELPERS_H_ + +#include "util/util.h" + +#define CERT_AUTH_DEFAULT_MATCHING_RULE "KRB5:<EKU>clientAuth" + +errno_t pam_initgr_cache_set(struct tevent_context *ev, + hash_table_t *id_table, + char *name, + long timeout); + +/* Returns EOK if the cache is still valid + * Returns ENOENT if the user is not found or is expired + * May report other errors if the hash lookup fails. + */ +errno_t pam_initgr_check_timeout(hash_table_t *id_table, + char *name); + +#endif /* PAM_HELPERS_H_ */ diff --git a/src/responder/pam/pam_prompting_config.c b/src/responder/pam/pam_prompting_config.c new file mode 100644 index 0000000..7d0362f --- /dev/null +++ b/src/responder/pam/pam_prompting_config.c @@ -0,0 +1,309 @@ +/* + SSSD + + PAM Responder - helpers for PAM prompting configuration + + Copyright (C) Sumit Bose <sbose@redhat.com> 2019 + + 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 "util/util.h" +#include "util/sss_pam_data.h" +#include "confdb/confdb.h" +#include "sss_client/sss_cli.h" +#include "responder/pam/pamsrv.h" + +#define DEFAULT_PASSKEY_PROMPT_INTERACTIVE _("Insert your Passkey device, then press ENTER.") +#define DEFAULT_PASSKEY_PROMPT_TOUCH _("Please touch the device.") + +typedef errno_t (pam_set_prompting_fn_t)(TALLOC_CTX *, struct confdb_ctx *, + const char *, + struct prompt_config ***); + + +static errno_t pam_set_password_prompting_options(TALLOC_CTX *tmp_ctx, + struct confdb_ctx *cdb, + const char *section, + struct prompt_config ***pc_list) +{ + int ret; + char *value = NULL; + + ret = confdb_get_string(cdb, tmp_ctx, section, CONFDB_PC_PASSWORD_PROMPT, + NULL, &value); + if (ret == EOK && value != NULL) { + ret = pc_list_add_password(pc_list, value); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "pc_list_add_password failed.\n"); + } + return ret; + } + + return ENOENT; +} + +static errno_t pam_set_2fa_prompting_options(TALLOC_CTX *tmp_ctx, + struct confdb_ctx *cdb, + const char *section, + struct prompt_config ***pc_list) +{ + bool single_2fa_prompt = false; + char *first_prompt = NULL; + char *second_prompt = NULL; + int ret; + + + ret = confdb_get_bool(cdb, section, CONFDB_PC_2FA_SINGLE_PROMPT, false, + &single_2fa_prompt); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "confdb_get_bool failed, using defaults"); + } + ret = confdb_get_string(cdb, tmp_ctx, section, CONFDB_PC_2FA_1ST_PROMPT, + NULL, &first_prompt); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "confdb_get_string failed, using defaults"); + } + + if (single_2fa_prompt) { + ret = pc_list_add_2fa_single(pc_list, first_prompt); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "pc_list_add_2fa_single failed.\n"); + } + return ret; + } else { + ret = confdb_get_string(cdb, tmp_ctx, section, CONFDB_PC_2FA_2ND_PROMPT, + NULL, &second_prompt); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "confdb_get_string failed, using defaults"); + } + + ret = pc_list_add_2fa(pc_list, first_prompt, second_prompt); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "pc_list_add_2fa failed.\n"); + } + return ret; + } + + return ENOENT; +} + +static errno_t pam_set_passkey_prompting_options(TALLOC_CTX *tmp_ctx, + struct confdb_ctx *cdb, + const char *section, + struct prompt_config ***pc_list) +{ + bool passkey_interactive = false; + char *passkey_interactive_prompt = NULL; + bool passkey_touch = false; + char *passkey_touch_prompt = NULL; + int ret; + + + ret = confdb_get_bool(cdb, section, CONFDB_PC_PASSKEY_INTERACTIVE, false, + &passkey_interactive); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "confdb_get_bool failed, using defaults"); + } + + if (passkey_interactive) { + ret = confdb_get_string(cdb, tmp_ctx, section, CONFDB_PC_PASSKEY_INTERACTIVE_PROMPT, + DEFAULT_PASSKEY_PROMPT_INTERACTIVE, &passkey_interactive_prompt); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "confdb_get_string failed, using defaults"); + } + } + + ret = confdb_get_bool(cdb, section, CONFDB_PC_PASSKEY_TOUCH, false, + &passkey_touch); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "confdb_get_bool failed, using defaults"); + } + + if (passkey_touch) { + ret = confdb_get_string(cdb, tmp_ctx, section, CONFDB_PC_PASSKEY_TOUCH_PROMPT, + DEFAULT_PASSKEY_PROMPT_TOUCH, &passkey_touch_prompt); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "confdb_get_string failed, using defaults"); + } + } + + ret = pc_list_add_passkey(pc_list, passkey_interactive_prompt, passkey_touch_prompt); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "pc_list_add_passkey_touch failed.\n"); + } + + return ret; +} +static errno_t pam_set_prompting_options(struct confdb_ctx *cdb, + const char *service_name, + char **sections, + int num_sections, + const char *section_path, + pam_set_prompting_fn_t *setter, + struct prompt_config ***pc_list) +{ + char *dummy; + size_t c; + bool global = false; + bool specific = false; + char *section = NULL; + int ret; + char *last; + TALLOC_CTX *tmp_ctx = NULL; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + + dummy = talloc_asprintf(tmp_ctx, "%s/%s", section_path, + service_name); + for (c = 0; c < num_sections; c++) { + if (strcmp(sections[c], section_path) == 0) { + global = true; + } + if (dummy != NULL && strcmp(sections[c], dummy) == 0) { + specific = true; + } + } + + section = talloc_asprintf(tmp_ctx, "%s/%s", CONFDB_PC_CONF_ENTRY, dummy); + if (section == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = ENOENT; + if (specific) { + ret = setter(tmp_ctx, cdb, section, pc_list); + } + if (global && ret == ENOENT) { + last = strrchr(section, '/'); + if (last != NULL) { + *last = '\0'; + ret = setter(tmp_ctx, cdb, section, pc_list); + } + } + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "setter failed.\n"); + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t pam_eval_prompting_config(struct pam_ctx *pctx, struct pam_data *pd) +{ + int ret; + struct prompt_config **pc_list = NULL; + int resp_len; + uint8_t *resp_data = NULL; + struct pam_resp_auth_type types; + + if (pctx->num_prompting_config_sections == 0) { + DEBUG(SSSDBG_TRACE_ALL, "No prompting configuration found.\n"); + return EOK; + } + + ret = pam_get_auth_types(pd, &types); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to get authentication types\n"); + goto done; + } + + if (types.passkey_auth) { + ret = pam_set_prompting_options(pctx->rctx->cdb, pd->service, + pctx->prompting_config_sections, + pctx->num_prompting_config_sections, + CONFDB_PC_TYPE_PASSKEY, + pam_set_passkey_prompting_options, + &pc_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "pam_set_prompting_options failed.\n"); + goto done; + } + } + + if (types.cert_auth) { + /* If certificate based authentication is possilbe, i.e. a Smartcard + * or similar with the mapped certificate is available we currently + * prefer this authentication type unconditionally. If other types + * should be used the Smartcard can be removed during authentication. + * Since there currently are no specific options for cert_auth we are + * done. */ + ret = EOK; + goto done; + } + + /* If OTP and password auth are possible we currently prefer OTP. */ + if (types.otp_auth) { + ret = pam_set_prompting_options(pctx->rctx->cdb, pd->service, + pctx->prompting_config_sections, + pctx->num_prompting_config_sections, + CONFDB_PC_TYPE_2FA, + pam_set_2fa_prompting_options, + &pc_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "pam_set_prompting_options failed.\n"); + goto done; + } + } + + if (types.password_auth) { + ret = pam_set_prompting_options(pctx->rctx->cdb, pd->service, + pctx->prompting_config_sections, + pctx->num_prompting_config_sections, + CONFDB_PC_TYPE_PASSWORD, + pam_set_password_prompting_options, + &pc_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "pam_set_prompting_options failed.\n"); + goto done; + } + } + + if (pc_list != NULL) { + ret = pam_get_response_prompt_config(pc_list, &resp_len, &resp_data); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "pam_get_response_prompt_config failed.\n"); + goto done; + } + + ret = pam_add_response(pd, SSS_PAM_PROMPT_CONFIG, resp_len, resp_data); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "pam_add_response failed.\n"); + goto done; + } + } + + ret = EOK; +done: + free(resp_data); + pc_list_free(pc_list); + + return ret; +} diff --git a/src/responder/pam/pamsrv.c b/src/responder/pam/pamsrv.c new file mode 100644 index 0000000..73ebb0a --- /dev/null +++ b/src/responder/pam/pamsrv.c @@ -0,0 +1,529 @@ +/* + SSSD + + PAM Responder + + Copyright (C) Simo Sorce <ssorce@redhat.com> 2009 + Copyright (C) Sumit Bose <sbose@redhat.com> 2009 + + 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 <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <string.h> +#include <sys/time.h> +#include <errno.h> +#include <popt.h> +#include <dbus/dbus.h> + +#include "config.h" +#include "util/util.h" +#include "db/sysdb.h" +#include "confdb/confdb.h" +#include "responder/common/responder_packet.h" +#include "providers/data_provider.h" +#include "responder/pam/pamsrv.h" +#include "responder/common/negcache.h" +#include "sss_iface/sss_iface_async.h" + +#define DEFAULT_PAM_FD_LIMIT 8192 +#define ALL_UIDS_ALLOWED "all" +#define ALL_DOMAINS_ARE_PUBLIC "all" +#define NO_DOMAINS_ARE_PUBLIC "none" +#define DEFAULT_ALLOWED_UIDS ALL_UIDS_ALLOWED +#define DEFAULT_PAM_CERT_AUTH false +#define DEFAULT_PAM_PASSKEY_AUTH true +#define DEFAULT_PAM_CERT_DB_PATH SYSCONFDIR"/sssd/pki/sssd_auth_ca_db.pem" +#define DEFAULT_PAM_INITGROUPS_SCHEME "no_session" + +static errno_t get_trusted_uids(struct pam_ctx *pctx) +{ + char *uid_str; + errno_t ret; + + ret = confdb_get_string(pctx->rctx->cdb, pctx->rctx, + CONFDB_PAM_CONF_ENTRY, CONFDB_PAM_TRUSTED_USERS, + DEFAULT_ALLOWED_UIDS, &uid_str); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to get allowed UIDs.\n"); + goto done; + } + + if (strcmp(uid_str, ALL_UIDS_ALLOWED) == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "All UIDs are allowed.\n"); + pctx->trusted_uids_count = 0; + } else { + ret = csv_string_to_uid_array(pctx->rctx, uid_str, + &pctx->trusted_uids_count, + &pctx->trusted_uids); + } + + talloc_free(uid_str); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to set allowed UIDs.\n"); + goto done; + } + +done: + return ret; +} + +static errno_t get_public_domains(struct pam_ctx *pctx) +{ + char *domains_str = NULL; + errno_t ret; + + ret = confdb_get_string(pctx->rctx->cdb, pctx->rctx, + CONFDB_PAM_CONF_ENTRY, CONFDB_PAM_PUBLIC_DOMAINS, + NO_DOMAINS_ARE_PUBLIC, &domains_str); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to get allowed UIDs.\n"); + goto done; + } + + if (strcmp(domains_str, ALL_DOMAINS_ARE_PUBLIC) == 0) { /* all */ + /* copy all domains */ + ret = get_dom_names(pctx, + pctx->rctx->domains, + &pctx->public_domains, + &pctx->public_domains_count); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "get_dom_names failed.\n"); + goto done; + } + } else if (strcmp(domains_str, NO_DOMAINS_ARE_PUBLIC) == 0) { /* none */ + pctx->public_domains = NULL; + pctx->public_domains_count = 0; + } else { + ret = split_on_separator(pctx, domains_str, ',', true, false, + &pctx->public_domains, + &pctx->public_domains_count); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "split_on_separator failed [%d][%s].\n", + ret, strerror(ret)); + goto done; + } + } + + ret = EOK; + +done: + talloc_free(domains_str); + return ret; +} + +static errno_t get_app_services(struct pam_ctx *pctx) +{ + errno_t ret; + + ret = confdb_get_string_as_list(pctx->rctx->cdb, pctx, + CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_APP_SERVICES, + &pctx->app_services); + if (ret == ENOENT) { + pctx->app_services = talloc_zero_array(pctx, char *, 1); + if (pctx->app_services == NULL) { + return ENOMEM; + } + /* Allocating an empty array makes it easier for the consumer + * to iterate over it + */ + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot read "CONFDB_PAM_APP_SERVICES" [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + return EOK; +} + +static void pam_get_domains_callback(void *pvt) +{ + struct pam_ctx *pctx; + int ret; + + pctx = talloc_get_type(pvt, struct pam_ctx); + ret = p11_refresh_certmap_ctx(pctx, pctx->rctx->domains); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "p11_refresh_certmap_ctx failed.\n"); + } +} + +static int pam_process_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct confdb_ctx *cdb, + int pipe_fd, int priv_pipe_fd) +{ + struct resp_ctx *rctx; + struct sss_cmd_table *pam_cmds; + struct pam_ctx *pctx; + int ret; + int id_timeout; + int fd_limit; + char *tmpstr = NULL; + + pam_cmds = get_pam_cmds(); + ret = sss_process_init(mem_ctx, ev, cdb, + pam_cmds, + SSS_PAM_SOCKET_NAME, pipe_fd, + SSS_PAM_PRIV_SOCKET_NAME, priv_pipe_fd, + CONFDB_PAM_CONF_ENTRY, + SSS_BUS_PAM, SSS_PAM_SBUS_SERVICE_NAME, + sss_connection_setup, + &rctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "sss_process_init() failed\n"); + return ret; + } + + pctx = talloc_zero(rctx, struct pam_ctx); + if (!pctx) { + ret = ENOMEM; + goto done; + } + + pctx->rctx = rctx; + pctx->rctx->pvt_ctx = pctx; + + ret = get_trusted_uids(pctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "get_trusted_uids failed: %d:[%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = get_public_domains(pctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "get_public_domains failed: %d:[%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = get_app_services(pctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "get_app_services failed: %d:[%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + /* Set up the PAM identity timeout */ + ret = confdb_get_int(cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_ID_TIMEOUT, 5, + &id_timeout); + if (ret != EOK) goto done; + + pctx->id_timeout = (size_t)id_timeout; + + ret = sss_ncache_prepopulate(pctx->rctx->ncache, cdb, pctx->rctx); + if (ret != EOK) { + goto done; + } + + /* Create table for initgroup lookups */ + ret = sss_hash_create(pctx, 0, &pctx->id_table); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Could not create initgroups hash table: [%s]\n", + strerror(ret)); + goto done; + } + + /* Set up file descriptor limits */ + ret = confdb_get_int(pctx->rctx->cdb, + CONFDB_PAM_CONF_ENTRY, + CONFDB_SERVICE_FD_LIMIT, + DEFAULT_PAM_FD_LIMIT, + &fd_limit); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to set up file descriptor limit\n"); + goto done; + } + responder_set_fd_limit(fd_limit); + + ret = schedule_get_domains_task(rctx, rctx->ev, rctx, pctx->rctx->ncache, + pam_get_domains_callback, pctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "schedule_get_domains_tasks failed.\n"); + goto done; + } + + /* Check if there is a prompting configuration */ + pctx->prompting_config_sections = NULL; + pctx->num_prompting_config_sections = 0; + ret = confdb_get_sub_sections(pctx, pctx->rctx->cdb, CONFDB_PC_CONF_ENTRY, + &pctx->prompting_config_sections, + &pctx->num_prompting_config_sections); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "confdb_get_sub_sections failed, not fatal.\n"); + } + + /* Check if certificate based authentication is enabled */ + ret = confdb_get_bool(pctx->rctx->cdb, + CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_CERT_AUTH, + DEFAULT_PAM_CERT_AUTH, + &pctx->cert_auth); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to determine get cert db path.\n"); + goto done; + } + + if (pctx->cert_auth) { + ret = p11_child_init(pctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "p11_child_init failed.\n"); + goto done; + } + + ret = confdb_get_string(pctx->rctx->cdb, pctx, + CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_CERT_DB_PATH, + DEFAULT_PAM_CERT_DB_PATH, + &pctx->ca_db); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to determine if certificate based authentication is " \ + "enabled or not.\n"); + goto done; + } + + } + + /* Check if passkey authentication is enabled */ + ret = confdb_get_bool(pctx->rctx->cdb, + CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_PASSKEY_AUTH, + DEFAULT_PAM_PASSKEY_AUTH, + &pctx->passkey_auth); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to check if passkey authentication is " \ + "enabled.\n"); + goto done; + } + + if (pctx->cert_auth + || pctx->passkey_auth + || pctx->num_prompting_config_sections != 0) { + ret = create_preauth_indicator(); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to create pre-authentication indicator file, " + "Smartcard/passkey authentication or configured prompting might " + "not work as expected.\n"); + } + } + + ret = confdb_get_string(pctx->rctx->cdb, pctx, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_INITGROUPS_SCHEME, + DEFAULT_PAM_INITGROUPS_SCHEME, &tmpstr); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to determine initgroups scheme.\n"); + goto done; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "Found value [%s] for option [%s].\n", tmpstr, + CONFDB_PAM_INITGROUPS_SCHEME); + + if (tmpstr == NULL) { + pctx->initgroups_scheme = PAM_INITGR_NO_SESSION; + } else { + pctx->initgroups_scheme = pam_initgroups_string_to_enum(tmpstr); + if (pctx->initgroups_scheme == PAM_INITGR_INVALID) { + DEBUG(SSSDBG_FATAL_FAILURE, "Unknown value [%s] for option %s.\n", + tmpstr, CONFDB_PAM_INITGROUPS_SCHEME); + ret = EINVAL; + goto done; + } + } + + ret = confdb_get_string(pctx->rctx->cdb, pctx, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_GSSAPI_SERVICES, "-", &tmpstr); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to determine gssapi services.\n"); + goto done; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "Found value [%s] for option [%s].\n", tmpstr, + CONFDB_PAM_GSSAPI_SERVICES); + + if (tmpstr != NULL) { + ret = split_on_separator(pctx, tmpstr, ',', true, true, + &pctx->gssapi_services, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "split_on_separator() failed [%d]: [%s].\n", ret, + sss_strerror(ret)); + goto done; + } + } + + ret = confdb_get_bool(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_GSSAPI_CHECK_UPN, true, + &pctx->gssapi_check_upn); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to read %s [%d]: %s\n", + CONFDB_PAM_GSSAPI_CHECK_UPN, ret, sss_strerror(ret)); + goto done; + } + + ret = confdb_get_string(pctx->rctx->cdb, pctx, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_GSSAPI_INDICATORS_MAP, "-", &tmpstr); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to determine gssapi services.\n"); + goto done; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "Found value [%s] for option [%s].\n", tmpstr, + CONFDB_PAM_GSSAPI_INDICATORS_MAP); + + if (tmpstr != NULL) { + ret = split_on_separator(pctx, tmpstr, ',', true, true, + &pctx->gssapi_indicators_map, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "split_on_separator() failed [%d]: [%s].\n", ret, + sss_strerror(ret)); + goto done; + } + } + + /* The responder is initialized. Now tell it to the monitor. */ + ret = sss_monitor_service_init(rctx, rctx->ev, SSS_BUS_PAM, + SSS_PAM_SBUS_SERVICE_NAME, + SSS_PAM_SBUS_SERVICE_VERSION, + MT_SVC_SERVICE, + &rctx->last_request_time, &rctx->mon_conn); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error setting up message bus\n"); + goto done; + } + + ret = sss_resp_register_service_iface(rctx); + if (ret != EOK) { + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(rctx); + } + return ret; +} + +int main(int argc, const char *argv[]) +{ + int opt; + poptContext pc; + char *opt_logger = NULL; + struct main_context *main_ctx; + int ret; + uid_t uid = 0; + gid_t gid = 0; + int pipe_fd = -1; + int priv_pipe_fd = -1; + + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_MAIN_OPTS + SSSD_LOGGER_OPTS + SSSD_SERVER_OPTS(uid, gid) + SSSD_RESPONDER_OPTS + POPT_TABLEEND + }; + + /* Set debug level to invalid value so we can decide if -d 0 was used. */ + debug_level = SSSDBG_INVALID; + + umask(DFL_RSP_UMASK); + + pc = poptGetContext(argv[0], argc, argv, long_options, 0); + while((opt = poptGetNextOpt(pc)) != -1) { + switch(opt) { + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + return 1; + } + } + + poptFreeContext(pc); + + /* set up things like debug, signals, daemonization, etc. */ + debug_log_file = "sssd_pam"; + DEBUG_INIT(debug_level, opt_logger); + + if (!is_socket_activated()) { + /* Create pipe file descriptors here before privileges are dropped + * in server_setup() */ + ret = create_pipe_fd(SSS_PAM_SOCKET_NAME, &pipe_fd, SCKT_RSP_UMASK); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "create_pipe_fd failed [%d]: %s.\n", + ret, sss_strerror(ret)); + return 2; + } + + ret = create_pipe_fd(SSS_PAM_PRIV_SOCKET_NAME, &priv_pipe_fd, + DFL_RSP_UMASK); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "create_pipe_fd failed (privileged pipe) [%d]: %s.\n", + ret, sss_strerror(ret)); + return 2; + } + } + + /* server_setup() might switch to an unprivileged user, so the permissions + * for p11_child.log have to be fixed first. */ + ret = chown_debug_file("p11_child", uid, gid); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot chown the p11_child debug file, " + "debugging might not work!\n"); + } + + ret = server_setup("pam", true, 0, uid, gid, CONFDB_PAM_CONF_ENTRY, + &main_ctx, false); + if (ret != EOK) return 2; + + ret = die_if_parent_died(); + if (ret != EOK) { + /* This is not fatal, don't return */ + DEBUG(SSSDBG_OP_FAILURE, + "Could not set up to exit when parent process does\n"); + } + + ret = pam_process_init(main_ctx, + main_ctx->event_ctx, + main_ctx->confdb_ctx, + pipe_fd, priv_pipe_fd); + if (ret != EOK) return 3; + + /* loop on main */ + server_loop(main_ctx); + + return 0; +} + diff --git a/src/responder/pam/pamsrv.h b/src/responder/pam/pamsrv.h new file mode 100644 index 0000000..7013a8e --- /dev/null +++ b/src/responder/pam/pamsrv.h @@ -0,0 +1,173 @@ +/* + Authors: + Simo Sorce <ssorce@redhat.com> + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2009 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/>. +*/ + +#ifndef __PAMSRV_H__ +#define __PAMSRV_H__ + +#include <security/pam_appl.h> +#include "util/util.h" +#include "responder/common/responder.h" +#include "responder/common/cache_req/cache_req.h" +#include "lib/certmap/sss_certmap.h" + +struct pam_auth_req; + +typedef void (pam_dp_callback_t)(struct pam_auth_req *preq); + +enum pam_initgroups_scheme { + PAM_INITGR_NEVER, + PAM_INITGR_NO_SESSION, + PAM_INITGR_ALWAYS, + PAM_INITGR_INVALID +}; + +struct pam_ctx { + struct resp_ctx *rctx; + time_t id_timeout; + hash_table_t *id_table; + size_t trusted_uids_count; + uid_t *trusted_uids; + + /* List of domains that are accessible even for untrusted users. */ + char **public_domains; + int public_domains_count; + + /* What services are permitted to access application domains */ + char **app_services; + + bool cert_auth; + char *ca_db; + struct sss_certmap_ctx *sss_certmap_ctx; + char **smartcard_services; + + /* parsed list of pam_response_filter option */ + char **pam_filter_opts; + + char **prompting_config_sections; + int num_prompting_config_sections; + + enum pam_initgroups_scheme initgroups_scheme; + + /* List of PAM services that are allowed to authenticate with GSSAPI. */ + char **gssapi_services; + /* List of authentication indicators associated with a PAM service */ + char **gssapi_indicators_map; + bool gssapi_check_upn; + bool passkey_auth; + struct pam_passkey_table_data *pk_table_data; +}; + +struct pam_auth_req { + struct cli_ctx *cctx; + struct sss_domain_info *domain; + enum cache_req_dom_type req_dom_type; + + struct pam_data *pd; + + pam_dp_callback_t *callback; + + bool is_uid_trusted; + void *data; + bool use_cached_auth; + /* whether cached authentication was tried and failed */ + bool cached_auth_failed; + + struct ldb_message *user_obj; + struct cert_auth_info *cert_list; + struct cert_auth_info *current_cert; + bool cert_auth_local; + + bool passkey_data_exists; + uint32_t client_id_num; +}; + +struct pam_resp_auth_type { + bool password_auth; + bool otp_auth; + bool cert_auth; + bool passkey_auth; +}; + +struct sss_cmd_table *get_pam_cmds(void); + +errno_t +pam_dp_send_req(struct pam_auth_req *preq); + +int pam_check_user_search(struct pam_auth_req *preq); +int pam_check_user_done(struct pam_auth_req *preq, int ret); +void pam_reply(struct pam_auth_req *preq); + +errno_t p11_child_init(struct pam_ctx *pctx); + +struct cert_auth_info; +const char *sss_cai_get_cert(struct cert_auth_info *i); +const char *sss_cai_get_token_name(struct cert_auth_info *i); +const char *sss_cai_get_module_name(struct cert_auth_info *i); +const char *sss_cai_get_key_id(struct cert_auth_info *i); +const char *sss_cai_get_label(struct cert_auth_info *i); +struct cert_auth_info *sss_cai_get_next(struct cert_auth_info *i); +struct ldb_result *sss_cai_get_cert_user_objs(struct cert_auth_info *i); +void sss_cai_set_cert_user_objs(struct cert_auth_info *i, + struct ldb_result *cert_user_objs); +void sss_cai_check_users(struct cert_auth_info **list, size_t *_cert_count, + size_t *_cert_user_count); + +struct tevent_req *pam_check_cert_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *ca_db, + time_t timeout, + const char *verify_opts, + struct sss_certmap_ctx *sss_certmap_ctx, + const char *uri, + struct pam_data *pd); +errno_t pam_check_cert_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + struct cert_auth_info **cert_list); + +errno_t add_pam_cert_response(struct pam_data *pd, struct sss_domain_info *dom, + const char *sysdb_username, + struct cert_auth_info *cert_info, + enum response_type type); + +bool may_do_cert_auth(struct pam_ctx *pctx, struct pam_data *pd); + +errno_t p11_refresh_certmap_ctx(struct pam_ctx *pctx, + struct sss_domain_info *domains); + +errno_t +pam_set_last_online_auth_with_curr_token(struct sss_domain_info *domain, + const char *username, + uint64_t value); + +errno_t filter_responses(struct pam_ctx *pctx, + struct response_data *resp_list, + struct pam_data *pd); + +errno_t pam_get_auth_types(struct pam_data *pd, + struct pam_resp_auth_type *_auth_types); +errno_t pam_eval_prompting_config(struct pam_ctx *pctx, struct pam_data *pd); + +enum pam_initgroups_scheme pam_initgroups_string_to_enum(const char *str); +const char *pam_initgroup_enum_to_string(enum pam_initgroups_scheme scheme); + +int pam_cmd_gssapi_init(struct cli_ctx *cli_ctx); +int pam_cmd_gssapi_sec_ctx(struct cli_ctx *cctx); + +#endif /* __PAMSRV_H__ */ diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c new file mode 100644 index 0000000..c23ea7b --- /dev/null +++ b/src/responder/pam/pamsrv_cmd.c @@ -0,0 +1,3085 @@ +/* + SSSD + + PAM Responder + + Copyright (C) Simo Sorce <ssorce@redhat.com> 2009 + Copyright (C) Sumit Bose <sbose@redhat.com> 2009 + + 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/>. +*/ + +#define _GNU_SOURCE + +#include <time.h> +#include <string.h> +#include "util/util.h" +#include "util/auth_utils.h" +#include "util/find_uid.h" +#include "util/sss_ptr_hash.h" +#include "db/sysdb.h" +#include "confdb/confdb.h" +#include "responder/common/responder_packet.h" +#include "responder/common/responder.h" +#include "responder/common/negcache.h" +#include "providers/data_provider.h" +#include "responder/pam/pamsrv.h" +#include "responder/pam/pamsrv_passkey.h" +#include "responder/pam/pam_helpers.h" +#include "responder/common/cache_req/cache_req.h" + +enum pam_verbosity { + PAM_VERBOSITY_NO_MESSAGES = 0, + PAM_VERBOSITY_IMPORTANT, + PAM_VERBOSITY_INFO, + PAM_VERBOSITY_DEBUG +}; + +#define DEFAULT_PAM_VERBOSITY PAM_VERBOSITY_IMPORTANT + +struct pam_initgroup_enum_str { + enum pam_initgroups_scheme scheme; + const char *option; +}; + +struct pam_initgroup_enum_str pam_initgroup_enum_str[] = { + { PAM_INITGR_NEVER, "never" }, + { PAM_INITGR_NO_SESSION, "no_session" }, + { PAM_INITGR_ALWAYS, "always" }, + { PAM_INITGR_INVALID, NULL } +}; + +enum pam_initgroups_scheme pam_initgroups_string_to_enum(const char *str) +{ + size_t c; + + for (c = 0 ; pam_initgroup_enum_str[c].option != NULL; c++) { + if (strcasecmp(pam_initgroup_enum_str[c].option, str) == 0) { + return pam_initgroup_enum_str[c].scheme; + } + } + + return PAM_INITGR_INVALID; +} + +const char *pam_initgroup_enum_to_string(enum pam_initgroups_scheme scheme) +{ + size_t c; + + for (c = 0 ; pam_initgroup_enum_str[c].option != NULL; c++) { + if (pam_initgroup_enum_str[c].scheme == scheme) { + return pam_initgroup_enum_str[c].option; + } + } + + return "(NULL)"; +} + +static errno_t +pam_null_last_online_auth_with_curr_token(struct sss_domain_info *domain, + const char *username); +static errno_t +pam_get_last_online_auth_with_curr_token(struct sss_domain_info *domain, + const char *name, + uint64_t *_value); + +void pam_reply(struct pam_auth_req *preq); + +static errno_t check_cert(TALLOC_CTX *mctx, + struct tevent_context *ev, + struct pam_ctx *pctx, + struct pam_auth_req *preq, + struct pam_data *pd); + +int pam_check_user_done(struct pam_auth_req *preq, int ret); + +static errno_t pack_user_info_msg(TALLOC_CTX *mem_ctx, + const char *user_error_message, + size_t *resp_len, + uint8_t **_resp) +{ + uint32_t resp_type = SSS_PAM_USER_INFO_ACCOUNT_EXPIRED; + size_t err_len; + uint8_t *resp; + size_t p; + + err_len = strlen(user_error_message); + *resp_len = 2 * sizeof(uint32_t) + err_len; + resp = talloc_size(mem_ctx, *resp_len); + if (resp == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_size failed.\n"); + return ENOMEM; + } + + p = 0; + SAFEALIGN_SET_UINT32(&resp[p], resp_type, &p); + SAFEALIGN_SET_UINT32(&resp[p], err_len, &p); + safealign_memcpy(&resp[p], user_error_message, err_len, &p); + if (p != *resp_len) { + DEBUG(SSSDBG_FATAL_FAILURE, "Size mismatch\n"); + } + + *_resp = resp; + return EOK; +} + +static void inform_user(struct pam_data* pd, const char *pam_message) +{ + size_t msg_len; + uint8_t *msg; + errno_t ret; + + ret = pack_user_info_msg(pd, pam_message, &msg_len, &msg); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "pack_user_info_msg failed.\n"); + } else { + ret = pam_add_response(pd, SSS_PAM_USER_INFO, msg_len, msg); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + } + } +} + +static bool is_domain_requested(struct pam_data *pd, const char *domain_name) +{ + int i; + + /* If none specific domains got requested via pam, all domains are allowed. + * Which mimics the default/original behaviour. + */ + if (!pd->requested_domains) { + return true; + } + + for (i = 0; pd->requested_domains[i]; i++) { + if (strcasecmp(domain_name, pd->requested_domains[i])) { + continue; + } + + return true; + } + + return false; +} + +static int extract_authtok_v2(struct sss_auth_token *tok, + size_t data_size, uint8_t *body, size_t blen, + size_t *c) +{ + uint32_t auth_token_type; + uint32_t auth_token_length; + uint8_t *auth_token_data; + int ret = EOK; + + if (data_size < sizeof(uint32_t) || *c+data_size > blen || + SIZE_T_OVERFLOW(*c, data_size)) return EINVAL; + + SAFEALIGN_COPY_UINT32_CHECK(&auth_token_type, &body[*c], blen, c); + auth_token_length = data_size - sizeof(uint32_t); + auth_token_data = body+(*c); + + switch (auth_token_type) { + case SSS_AUTHTOK_TYPE_EMPTY: + sss_authtok_set_empty(tok); + break; + case SSS_AUTHTOK_TYPE_PASSWORD: + if (auth_token_length == 0) { + sss_authtok_set_empty(tok); + } else { + ret = sss_authtok_set_password(tok, (const char *)auth_token_data, + auth_token_length); + } + break; + case SSS_AUTHTOK_TYPE_2FA: + case SSS_AUTHTOK_TYPE_2FA_SINGLE: + case SSS_AUTHTOK_TYPE_SC_PIN: + case SSS_AUTHTOK_TYPE_SC_KEYPAD: + case SSS_AUTHTOK_TYPE_OAUTH2: + case SSS_AUTHTOK_TYPE_PASSKEY: + case SSS_AUTHTOK_TYPE_PASSKEY_KRB: + case SSS_AUTHTOK_TYPE_PASSKEY_REPLY: + ret = sss_authtok_set(tok, auth_token_type, + auth_token_data, auth_token_length); + break; + default: + return EINVAL; + } + + *c += auth_token_length; + + return ret; +} + +static int extract_string(char **var, size_t size, uint8_t *body, size_t blen, + size_t *c) { + uint8_t *str; + + if (*c+size > blen || SIZE_T_OVERFLOW(*c, size)) return EINVAL; + + str = body+(*c); + + if (str[size-1]!='\0') return EINVAL; + + /* If the string isn't valid UTF-8, fail */ + if (!sss_utf8_check(str, size-1)) { + return EINVAL; + } + + *c += size; + + *var = (char *) str; + + return EOK; +} + +static int extract_uint32_t(uint32_t *var, size_t size, uint8_t *body, + size_t blen, size_t *c) { + + if (size != sizeof(uint32_t) || *c+size > blen || SIZE_T_OVERFLOW(*c, size)) + return EINVAL; + + SAFEALIGN_COPY_UINT32_CHECK(var, &body[*c], blen, c); + + return EOK; +} + +static int pd_set_primary_name(const struct ldb_message *msg,struct pam_data *pd) +{ + const char *name; + + name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL); + if (!name) { + DEBUG(SSSDBG_CRIT_FAILURE, "A user with no name?\n"); + return EIO; + } + + if (strcmp(pd->user, name)) { + DEBUG(SSSDBG_TRACE_FUNC, "User's primary name is %s\n", name); + talloc_free(pd->user); + pd->user = talloc_strdup(pd, name); + if (!pd->user) return ENOMEM; + } + + return EOK; +} + +static int pam_parse_in_data_v2(struct pam_data *pd, + uint8_t *body, size_t blen) +{ + size_t c; + uint32_t type; + uint32_t size; + int ret; + uint32_t start; + uint32_t terminator; + char *requested_domains; + + if (blen < 4*sizeof(uint32_t)+2) { + DEBUG(SSSDBG_CRIT_FAILURE, "Received data is invalid.\n"); + return EINVAL; + } + + SAFEALIGN_COPY_UINT32(&start, body, NULL); + SAFEALIGN_COPY_UINT32(&terminator, body + blen - sizeof(uint32_t), NULL); + + if (start != SSS_START_OF_PAM_REQUEST + || terminator != SSS_END_OF_PAM_REQUEST) { + DEBUG(SSSDBG_CRIT_FAILURE, "Received data is invalid.\n"); + return EINVAL; + } + + c = sizeof(uint32_t); + do { + SAFEALIGN_COPY_UINT32_CHECK(&type, &body[c], blen, &c); + + if (type == SSS_END_OF_PAM_REQUEST) { + if (c != blen) return EINVAL; + } else { + SAFEALIGN_COPY_UINT32_CHECK(&size, &body[c], blen, &c); + /* the uint32_t end maker SSS_END_OF_PAM_REQUEST does not count to + * the remaining buffer */ + if (size > (blen - c - sizeof(uint32_t))) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid data size.\n"); + return EINVAL; + } + + switch(type) { + case SSS_PAM_ITEM_USER: + ret = extract_string(&pd->logon_name, size, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_SERVICE: + ret = extract_string(&pd->service, size, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_TTY: + ret = extract_string(&pd->tty, size, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_RUSER: + ret = extract_string(&pd->ruser, size, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_RHOST: + ret = extract_string(&pd->rhost, size, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_REQUESTED_DOMAINS: + ret = extract_string(&requested_domains, size, body, blen, + &c); + if (ret != EOK) return ret; + + ret = split_on_separator(pd, requested_domains, ',', true, + true, &pd->requested_domains, + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to parse requested_domains list!\n"); + return ret; + } + break; + case SSS_PAM_ITEM_CLI_PID: + ret = extract_uint32_t(&pd->cli_pid, size, + body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_CHILD_PID: + /* This is optional. */ + ret = extract_uint32_t(&pd->child_pid, size, + body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_AUTHTOK: + ret = extract_authtok_v2(pd->authtok, + size, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_NEWAUTHTOK: + ret = extract_authtok_v2(pd->newauthtok, + size, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_FLAGS: + ret = extract_uint32_t(&pd->cli_flags, size, + body, blen, &c); + if (ret != EOK) return ret; + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, + "Ignoring unknown data type [%d].\n", type); + c += size; + } + } + + } while(c < blen); + + return EOK; + +} + +static int pam_parse_in_data_v3(struct pam_data *pd, + uint8_t *body, size_t blen) +{ + int ret; + + ret = pam_parse_in_data_v2(pd, body, blen); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_parse_in_data_v2 failed.\n"); + return ret; + } + + if (pd->cli_pid == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing client PID.\n"); + return EINVAL; + } + + return EOK; +} + +static int extract_authtok_v1(struct sss_auth_token *tok, + uint8_t *body, size_t blen, size_t *c) +{ + uint32_t auth_token_type; + uint32_t auth_token_length; + uint8_t *auth_token_data; + int ret = EOK; + + SAFEALIGN_COPY_UINT32_CHECK(&auth_token_type, &body[*c], blen, c); + SAFEALIGN_COPY_UINT32_CHECK(&auth_token_length, &body[*c], blen, c); + auth_token_data = body+(*c); + + switch (auth_token_type) { + case SSS_AUTHTOK_TYPE_EMPTY: + sss_authtok_set_empty(tok); + break; + case SSS_AUTHTOK_TYPE_PASSWORD: + ret = sss_authtok_set_password(tok, (const char *)auth_token_data, + auth_token_length); + break; + default: + return EINVAL; + } + + *c += auth_token_length; + + return ret; +} + +static int pam_parse_in_data(struct pam_data *pd, + uint8_t *body, size_t blen) +{ + size_t start; + size_t end; + size_t last; + int ret; + + last = blen - 1; + end = 0; + + /* user name */ + for (start = end; end < last; end++) if (body[end] == '\0') break; + if (body[end++] != '\0') return EINVAL; + pd->logon_name = (char *) &body[start]; + + for (start = end; end < last; end++) if (body[end] == '\0') break; + if (body[end++] != '\0') return EINVAL; + pd->service = (char *) &body[start]; + + for (start = end; end < last; end++) if (body[end] == '\0') break; + if (body[end++] != '\0') return EINVAL; + pd->tty = (char *) &body[start]; + + for (start = end; end < last; end++) if (body[end] == '\0') break; + if (body[end++] != '\0') return EINVAL; + pd->ruser = (char *) &body[start]; + + for (start = end; end < last; end++) if (body[end] == '\0') break; + if (body[end++] != '\0') return EINVAL; + pd->rhost = (char *) &body[start]; + + ret = extract_authtok_v1(pd->authtok, body, blen, &end); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid auth token\n"); + return ret; + } + ret = extract_authtok_v1(pd->newauthtok, body, blen, &end); + if (ret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid new auth token\n"); + return ret; + } + + DEBUG_PAM_DATA(SSSDBG_CONF_SETTINGS, pd); + + return EOK; +} + +static errno_t +pam_get_local_auth_policy(struct sss_domain_info *domain, + const char *name, + bool *_sc_allow, + bool *_passkey_allow) +{ + TALLOC_CTX *tmp_ctx = NULL; + const char *attrs[] = { SYSDB_LOCAL_SMARTCARD_AUTH, SYSDB_LOCAL_PASSKEY_AUTH, NULL }; + struct ldb_message *ldb_msg; + bool sc_allow = false; + bool passkey_allow = false; + errno_t ret; + + if (name == NULL || *name == '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing user name.\n"); + ret = EINVAL; + goto done; + } + + if (domain->sysdb == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing sysdb db context.\n"); + ret = EINVAL; + goto done; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_user_by_name(tmp_ctx, domain, name, attrs, &ldb_msg); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_search_user_by_name failed [%d][%s].\n", + ret, strerror(ret)); + goto done; + } + + sc_allow = ldb_msg_find_attr_as_bool(ldb_msg, SYSDB_LOCAL_SMARTCARD_AUTH, + false); + + passkey_allow = ldb_msg_find_attr_as_bool(ldb_msg, SYSDB_LOCAL_PASSKEY_AUTH, + true); + + ret = EOK; + +done: + if (ret == EOK) { + *_sc_allow = sc_allow; + *_passkey_allow = passkey_allow; + } + + talloc_free(tmp_ctx); + return ret; +} +static errno_t set_local_auth_type(struct pam_auth_req *preq, + bool sc_allow, + bool passkey_allow) +{ + struct sysdb_attrs *attrs; + errno_t ret; + + attrs = sysdb_new_attrs(preq); + if (!attrs) { + ret = ENOMEM; + goto fail; + } + + ret = sysdb_attrs_add_bool(attrs, SYSDB_LOCAL_SMARTCARD_AUTH, sc_allow); + if (ret != EOK) { + goto fail; + } + + ret = sysdb_attrs_add_bool(attrs, SYSDB_LOCAL_PASSKEY_AUTH, passkey_allow); + if (ret != EOK) { + goto fail; + } + + ret = sysdb_set_user_attr(preq->domain, preq->pd->user, attrs, + SYSDB_MOD_REP); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "set_local_auth_type failed.\n"); + preq->pd->pam_status = PAM_SYSTEM_ERR; + goto fail; + } + + return EOK; + +fail: + return ret; +} +/*=Save-Last-Login-State===================================================*/ + +static errno_t set_last_login(struct pam_auth_req *preq) +{ + struct sysdb_attrs *attrs; + errno_t ret; + + attrs = sysdb_new_attrs(preq); + if (!attrs) { + ret = ENOMEM; + goto fail; + } + + ret = sysdb_attrs_add_time_t(attrs, SYSDB_LAST_ONLINE_AUTH, time(NULL)); + if (ret != EOK) { + goto fail; + } + + ret = sysdb_attrs_add_time_t(attrs, + SYSDB_LAST_ONLINE_AUTH_WITH_CURR_TOKEN, + time(NULL)); + if (ret != EOK) { + goto fail; + } + + ret = sysdb_attrs_add_time_t(attrs, SYSDB_LAST_LOGIN, time(NULL)); + if (ret != EOK) { + goto fail; + } + + ret = sysdb_set_user_attr(preq->domain, preq->pd->user, attrs, + SYSDB_MOD_REP); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "set_last_login failed.\n"); + preq->pd->pam_status = PAM_SYSTEM_ERR; + goto fail; + } else { + preq->pd->last_auth_saved = true; + } + preq->callback(preq); + + return EOK; + +fail: + return ret; +} + +static errno_t filter_responses_env(struct response_data *resp, + struct pam_data *pd, + char * const *pam_filter_opts) +{ + size_t c; + const char *var_name; + size_t var_name_len; + const char *service; + + if (pam_filter_opts == NULL) { + return EOK; + } + + for (c = 0; pam_filter_opts[c] != NULL; c++) { + if (strncmp(pam_filter_opts[c], "ENV", 3) != 0) { + continue; + } + + var_name = NULL; + var_name_len = 0; + service = NULL; + if (pam_filter_opts[c][3] != '\0') { + if (pam_filter_opts[c][3] != ':') { + /* Neither plain ENV nor ENV:, ignored */ + continue; + } + + var_name = pam_filter_opts[c] + 4; + /* check if there is a second ':' in the option and use the following + * data, if any, as service name. */ + service = strchr(var_name, ':'); + if (service == NULL) { + var_name_len = strlen(var_name); + } else { + var_name_len = service - var_name; + + service++; + /* handle empty service name "ENV:var:" */ + if (*service == '\0') { + service = NULL; + } + } + } + /* handle empty var name "ENV:" or "ENV::service" */ + if (var_name_len == 0) { + var_name = NULL; + } + + DEBUG(SSSDBG_TRACE_ALL, + "Found PAM ENV filter for variable [%.*s] and service [%s].\n", + (int) var_name_len, + (var_name ? var_name : "(NULL)"), + (service ? service : "(NULL)")); + + if (service != NULL && pd->service != NULL + && strcmp(service, pd->service) != 0) { + /* current service does not match the filter */ + continue; + } + + if (var_name == NULL) { + /* All environment variables should be filtered */ + resp->do_not_send_to_client = true; + continue; + } + + if (resp->len > var_name_len && resp->data[var_name_len] == '=' + && memcmp(resp->data, var_name, var_name_len) == 0) { + resp->do_not_send_to_client = true; + } + } + + return EOK; +} + +errno_t filter_responses(struct pam_ctx *pctx, + struct response_data *resp_list, + struct pam_data *pd) +{ + int ret; + struct response_data *resp; + uint32_t user_info_type; + int64_t expire_date = 0; + int pam_verbosity = DEFAULT_PAM_VERBOSITY; + char **new_opts; + size_t c; + const char *default_pam_response_filter[] = { "ENV:KRB5CCNAME:sudo", + "ENV:KRB5CCNAME:sudo-i", + NULL }; + + ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_VERBOSITY, DEFAULT_PAM_VERBOSITY, + &pam_verbosity); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read PAM verbosity, not fatal.\n"); + pam_verbosity = DEFAULT_PAM_VERBOSITY; + } + + if (pctx->pam_filter_opts == NULL) { + ret = confdb_get_string_as_list(pctx->rctx->cdb, pctx, + CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_RESPONSE_FILTER, + &pctx->pam_filter_opts); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read values of [%s], not fatal.\n", + CONFDB_PAM_RESPONSE_FILTER); + pctx->pam_filter_opts = NULL; + } else { + if (pctx->pam_filter_opts == NULL + || *pctx->pam_filter_opts[0] == '+' + || *pctx->pam_filter_opts[0] == '-') { + ret = mod_defaults_list(pctx, default_pam_response_filter, + pctx->pam_filter_opts, &new_opts); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to modify [%s] defaults.\n", + CONFDB_PAM_RESPONSE_FILTER); + return ret; + } + talloc_free(pctx->pam_filter_opts); + pctx->pam_filter_opts = new_opts; + } + } + + if (pctx->pam_filter_opts == NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, "No PAM response filter set.\n"); + } else { + /* Make sure there are no '+' or '-' prefixes anymore */ + for (c = 0; pctx->pam_filter_opts[c] != NULL; c++) { + if (*pctx->pam_filter_opts[0] == '+' + || *pctx->pam_filter_opts[0] == '-') { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unsupport mix of prefixed and not prefixed " + "values of [%s].\n", CONFDB_PAM_RESPONSE_FILTER); + return EINVAL; + } + DEBUG(SSSDBG_CONF_SETTINGS, + "PAM response filter: [%s].\n", + pctx->pam_filter_opts[c]); + } + } + } + + resp = resp_list; + while(resp != NULL) { + if (resp->type == SSS_PAM_USER_INFO) { + if (resp->len < sizeof(uint32_t)) { + DEBUG(SSSDBG_CRIT_FAILURE, "User info entry is too short.\n"); + ret = EINVAL; + goto done; + } + + if (pam_verbosity == PAM_VERBOSITY_NO_MESSAGES) { + resp->do_not_send_to_client = true; + resp = resp->next; + continue; + } + + memcpy(&user_info_type, resp->data, sizeof(uint32_t)); + + resp->do_not_send_to_client = false; + switch (user_info_type) { + case SSS_PAM_USER_INFO_OFFLINE_AUTH: + if (resp->len != sizeof(uint32_t) + sizeof(int64_t)) { + DEBUG(SSSDBG_CRIT_FAILURE, + "User info offline auth entry is " + "too short.\n"); + ret = EINVAL; + goto done; + } + memcpy(&expire_date, resp->data + sizeof(uint32_t), + sizeof(int64_t)); + if ((expire_date == 0 && + pam_verbosity < PAM_VERBOSITY_INFO) || + (expire_date > 0 && + pam_verbosity < PAM_VERBOSITY_IMPORTANT)) { + resp->do_not_send_to_client = true; + } + + break; + default: + DEBUG(SSSDBG_TRACE_LIBS, + "User info type [%d] not filtered.\n", + user_info_type); + } + } else if (resp->type == SSS_PAM_ENV_ITEM) { + resp->do_not_send_to_client = false; + ret = filter_responses_env(resp, pd, pctx->pam_filter_opts); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "filter_responses_env failed.\n"); + goto done; + } + } else if (resp->type & SSS_SERVER_INFO) { + resp->do_not_send_to_client = true; + } + + resp = resp->next; + } + + ret = EOK; +done: + + return ret; +} + +static void do_not_send_cert_info(struct pam_data *pd) +{ + struct response_data *resp; + + resp = pd->resp_list; + while (resp != NULL) { + switch (resp->type) { + case SSS_PAM_CERT_INFO: + case SSS_PAM_CERT_INFO_WITH_HINT: + resp->do_not_send_to_client = true; + break; + default: + break; + } + resp = resp->next; + } +} + +static void evaluate_pam_resp_list(struct pam_data *pd, + struct pam_resp_auth_type *_auth_types, + bool *_found_cert_info) +{ + struct response_data *resp; + struct pam_resp_auth_type types = {0}; + bool found_cert_info = false; + + resp = pd->resp_list; + while (resp != NULL) { + switch (resp->type) { + case SSS_PAM_OTP_INFO: + types.otp_auth = true; + break; + case SSS_PAM_CERT_INFO: + case SSS_PAM_CERT_INFO_WITH_HINT: + found_cert_info = true; + break; + case SSS_PAM_PASSKEY_INFO: + case SSS_PAM_PASSKEY_KRB_INFO: + types.passkey_auth = true; + break; + case SSS_PASSWORD_PROMPTING: + types.password_auth = true; + break; + case SSS_CERT_AUTH_PROMPTING: + types.cert_auth = true; + break; + default: + break; + } + resp = resp->next; + } + + if (_auth_types != NULL) { + *_auth_types = types; + } + if (_found_cert_info != NULL) { + *_found_cert_info = found_cert_info; + } +} + +static void evalute_sending_cert_info(struct pam_data *pd) +{ + struct pam_resp_auth_type types = {0}; + bool found_cert_info = false; + + evaluate_pam_resp_list(pd, &types, &found_cert_info); + + if (found_cert_info && !types.cert_auth) { + do_not_send_cert_info(pd); + } +} + +errno_t pam_get_auth_types(struct pam_data *pd, + struct pam_resp_auth_type *_auth_types) +{ + int ret; + struct pam_resp_auth_type types = {0}; + + evaluate_pam_resp_list(pd, &types, NULL); + + if (!types.password_auth && !types.otp_auth && !types.cert_auth && !types.passkey_auth) { + /* If the backend cannot determine which authentication types are + * available the default would be to prompt for a password. */ + types.password_auth = true; + } + + DEBUG(SSSDBG_TRACE_ALL, "Authentication types for user [%s] and service " + "[%s]:%s%s%s%s\n", pd->user, pd->service, + types.password_auth ? " password": "", + types.otp_auth ? " two-factor" : "", + types.passkey_auth ? " passkey" : "", + types.cert_auth ? " smartcard" : ""); + + ret = EOK; + + *_auth_types = types; + + return ret; +} + +static errno_t pam_eval_local_auth_policy(TALLOC_CTX *mem_ctx, + struct pam_ctx *pctx, + struct pam_data *pd, + struct pam_auth_req *preq, + bool *_sc_allow, + bool *_passkey_allow, + char **_local_policy) { + + TALLOC_CTX *tmp_ctx; + errno_t ret; + const char *domain_cdb; + char *local_policy = NULL; + bool sc_allow = false; + bool passkey_allow = false; + struct pam_resp_auth_type auth_types; + char **opts; + size_t c; + +#ifdef BUILD_FILES_PROVIDER + if (is_files_provider(preq->domain)) { + *_sc_allow = true; + *_passkey_allow = false; + *_local_policy = NULL; + + return EOK; + } +#endif + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + /* Check local auth policy */ + domain_cdb = talloc_asprintf(tmp_ctx, CONFDB_DOMAIN_PATH_TMPL, preq->domain->name); + if (domain_cdb == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = confdb_get_string(pctx->rctx->cdb, tmp_ctx, domain_cdb, + CONFDB_DOMAIN_LOCAL_AUTH_POLICY, + "match", &local_policy); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to get the confdb local_auth_policy\n"); + return ret; + } + + /* "only" ignores online methods and allows all local ones */ + if (strcasecmp(local_policy, "only") == 0) { + sc_allow = true; + passkey_allow = true; + /* Match what the KDC supports and provides */ + } else if (strcasecmp(local_policy, "match") == 0) { + /* Don't overwrite the local auth type when offline */ + if (pd->pam_status == PAM_SUCCESS && pd->cmd == SSS_PAM_PREAUTH && + !is_domain_provider(preq->domain, "ldap")) { + ret = pam_get_auth_types(pd, &auth_types); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to get authentication types\n"); + goto done; + } + + if (auth_types.cert_auth) { + sc_allow = true; + } else if (auth_types.passkey_auth) { + passkey_allow = true; + } + + /* Store the local auth types, in case we go offline */ + if (!auth_types.password_auth) { + ret = set_local_auth_type(preq, sc_allow, passkey_allow); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to evaluate local auth policy\n"); + goto done; + } + } + } + + /* Read the latest auth types */ + ret = pam_get_local_auth_policy(preq->domain, preq->pd->user, + &sc_allow, &passkey_allow); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to get PAM local auth policy\n"); + goto done; + } + /* Check for enable */ + } else { + ret = split_on_separator(tmp_ctx, local_policy, ',', true, true, &opts, + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "split_on_separator failed [%d], %s.\n", + ret, sss_strerror(ret)); + goto done; + } + + for (c = 0; opts[c] != NULL; c++) { + if (strcasestr(opts[c], "passkey") != NULL) { + passkey_allow = strstr(opts[c], "enable") ? true : false; + } else if (strcasestr(opts[c], "smartcard") != NULL) { + sc_allow = strstr(opts[c], "enable") ? true : false; + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Unexpected local auth policy option [%s], " \ + "skipping.\n", opts[c]); + } + } + } + + /* if passkey is enabled but local Smartcard authentication is not but + * possible, the cert info data has to be remove as well if only local + * Smartcard authentication is possible. If Smartcard authentication + * is possible on the server side we have to keep it because the + * 'enable' option should only add local methods but not reject remote + * ones. */ + if (!sc_allow) { + evalute_sending_cert_info(pd); + } + + *_sc_allow = sc_allow; + *_passkey_allow = passkey_allow; + *_local_policy = talloc_steal(mem_ctx, local_policy); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static void pam_reply_delay(struct tevent_context *ev, struct tevent_timer *te, + struct timeval tv, void *pvt) +{ + struct pam_auth_req *preq; + + DEBUG(SSSDBG_CONF_SETTINGS, "pam_reply_delay get called.\n"); + + preq = talloc_get_type(pvt, struct pam_auth_req); + + pam_reply(preq); +} + +static errno_t get_password_for_cache_auth(struct sss_auth_token *authtok, + const char **password) +{ + int ret; + size_t pw_len; + const char *fa2; + size_t fa2_len; + + switch (sss_authtok_get_type(authtok)) { + case SSS_AUTHTOK_TYPE_PASSWORD: + ret = sss_authtok_get_password(authtok, password, NULL); + break; + case SSS_AUTHTOK_TYPE_2FA: + ret = sss_authtok_get_2fa(authtok, password, &pw_len, &fa2, &fa2_len); + break; + default: + DEBUG(SSSDBG_FATAL_FAILURE, "Unsupported auth token type [%d].\n", + sss_authtok_get_type(authtok)); + ret = EINVAL; + } + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to get password.\n"); + return ret; + } + + return EOK; +} + +static int pam_forwarder(struct cli_ctx *cctx, int pam_cmd); +static void pam_handle_cached_login(struct pam_auth_req *preq, int ret, + time_t expire_date, time_t delayed_until, bool cached_auth); + +/* + * Add a request to add a variable to the PAM user environment, containing the + * actual (not overridden) user shell, in case session recording is enabled. + */ +static int pam_reply_sr_export_shell(struct pam_auth_req *preq, + const char *var_name) +{ + int ret; + TALLOC_CTX *ctx = NULL; + bool enabled; + const char *enabled_str; + const char *shell; + char *buf; + + /* Create temporary talloc context */ + ctx = talloc_new(NULL); + if (ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed.\n"); + ret = ENOMEM; + goto done; + } + + /* Check if session recording is enabled */ + if (preq->cctx->rctx->sr_conf.scope == + SESSION_RECORDING_SCOPE_NONE) { + enabled = false; + } else { + enabled_str = ldb_msg_find_attr_as_string(preq->user_obj, + SYSDB_SESSION_RECORDING, NULL); + if (enabled_str == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "%s attribute not found\n", SYSDB_SESSION_RECORDING); + ret = ENOENT; + goto done; + } else if (strcmp(enabled_str, "TRUE") == 0) { + enabled = true; + } else if (strcmp(enabled_str, "FALSE") == 0) { + enabled = false; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "invalid value of %s attribute: %s\n", + SYSDB_SESSION_RECORDING, enabled_str); + ret = ENOENT; + goto done; + } + } + + /* Export original shell if recording is enabled and so it's overridden */ + if (enabled) { + /* Extract the shell */ + shell = sss_resp_get_shell_override(preq->user_obj, + preq->cctx->rctx, preq->domain); + if (shell == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "user has no shell\n"); + ret = ENOENT; + goto done; + } + + /* Format environment entry */ + buf = talloc_asprintf(ctx, "%s=%s", var_name, shell); + if (buf == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + /* Add request to add the entry to user environment */ + ret = pam_add_response(preq->pd, SSS_PAM_ENV_ITEM, + strlen(buf) + 1, (uint8_t *)buf); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + goto done; + } + } + + ret = EOK; + +done: + talloc_free(ctx); + return ret; +} + +void pam_reply(struct pam_auth_req *preq) +{ + struct cli_ctx *cctx; + struct cli_protocol *prctx; + uint8_t *body; + size_t blen; + int ret; + int32_t resp_c; + int32_t resp_size; + struct response_data *resp; + int p; + struct timeval tv; + struct tevent_timer *te; + struct pam_data *pd; + char *local_policy = NULL; + struct pam_ctx *pctx; + uint32_t user_info_type; + time_t exp_date = -1; + time_t delay_until = -1; + char* pam_account_expired_message; + char* pam_account_locked_message; + int pam_verbosity; + bool local_sc_auth_allow = false; + bool local_passkey_auth_allow = false; +#ifdef BUILD_PASSKEY + bool pk_preauth_done = false; +#endif /* BUILD_PASSKEY */ + + pd = preq->pd; + cctx = preq->cctx; + pctx = talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); + prctx = talloc_get_type(cctx->protocol_ctx, struct cli_protocol); + + ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_VERBOSITY, DEFAULT_PAM_VERBOSITY, + &pam_verbosity); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read PAM verbosity, not fatal.\n"); + pam_verbosity = DEFAULT_PAM_VERBOSITY; + } + + DEBUG(SSSDBG_TRACE_ALL, + "pam_reply initially called with result [%d]: %s. " + "this result might be changed during processing\n", + pd->pam_status, pam_strerror(NULL, pd->pam_status)); + + if (preq->domain != NULL && preq->domain->name != NULL) { + ret = pam_eval_local_auth_policy(cctx, pctx, pd, preq, + &local_sc_auth_allow, + &local_passkey_auth_allow, + &local_policy); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to evaluate local auth policy\n"); + goto done; + } + } + + /* Ignore local_auth_policy for the files provider, allow local + * smartcard auth (default behavior prior to local_auth_policy) */ + if (is_domain_provider(preq->domain, "files")) { + local_sc_auth_allow = true; + /* For the ldap auth provider we currently only support + * password based authentication */ + } else if (is_domain_provider(preq->domain, "ldap") && local_policy != NULL + && strcasecmp(local_policy, "match") == 0) { + local_passkey_auth_allow = false; + local_sc_auth_allow = false; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Local auth policy allowed: smartcard [%s], passkey [%s]\n", + local_sc_auth_allow ? "True" : "False", + local_passkey_auth_allow ? "True" : "False"); + + if (pd->cmd == SSS_PAM_AUTHENTICATE + && !preq->cert_auth_local + && (pd->pam_status == PAM_AUTHINFO_UNAVAIL + || pd->pam_status == PAM_NO_MODULE_DATA + || pd->pam_status == PAM_BAD_ITEM) + && may_do_cert_auth(pctx, pd)) { + /* We have Smartcard credentials and the backend indicates that it is + * offline (PAM_AUTHINFO_UNAVAIL) or cannot handle the credentials + * (PAM_BAD_ITEM), so let's try authentication against the Smartcard + * PAM_NO_MODULE_DATA is returned by the krb5 backend if no + * authentication method was found at all, this might happen if the + * user has a Smartcard assigned but the pkint plugin is not available + * on the client. */ + DEBUG(SSSDBG_IMPORTANT_INFO, + "Backend cannot handle Smartcard authentication, " + "trying local Smartcard authentication.\n"); + if (local_sc_auth_allow) { + preq->cert_auth_local = true; + ret = check_cert(cctx, cctx->ev, pctx, preq, pd); + pam_check_user_done(preq, ret); + return; + } else { + DEBUG(SSSDBG_IMPORTANT_INFO, + "Local smartcard auth not allowed by local_auth_policy"); + } + } + + if (pd->pam_status == PAM_AUTHINFO_UNAVAIL || preq->use_cached_auth) { + + switch(pd->cmd) { + case SSS_PAM_AUTHENTICATE: + if ((preq->domain != NULL) && + (preq->domain->cache_credentials == true) && + (pd->offline_auth == false)) { + const char *password = NULL; + bool use_cached_auth; + + /* backup value of preq->use_cached_auth*/ + use_cached_auth = preq->use_cached_auth; + /* set to false to avoid entering this branch when pam_reply() + * is recursively called from pam_handle_cached_login() */ + preq->use_cached_auth = false; + + /* do auth with offline credentials */ + pd->offline_auth = true; + + if (preq->domain->sysdb == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Fatal: Sysdb CTX not found for domain" + " [%s]!\n", preq->domain->name); + goto done; + } + + ret = get_password_for_cache_auth(pd->authtok, &password); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "get_password_and_type_for_cache_auth failed.\n"); + goto done; + } + + ret = sysdb_cache_auth(preq->domain, + pd->user, password, + pctx->rctx->cdb, false, + &exp_date, &delay_until); + + pam_handle_cached_login(preq, ret, exp_date, delay_until, + use_cached_auth); + return; + } + break; + case SSS_PAM_CHAUTHTOK_PRELIM: + case SSS_PAM_CHAUTHTOK: + DEBUG(SSSDBG_FUNC_DATA, + "Password change not possible while offline.\n"); + pd->pam_status = PAM_AUTHTOK_ERR; + user_info_type = SSS_PAM_USER_INFO_OFFLINE_CHPASS; + ret = pam_add_response(pd, SSS_PAM_USER_INFO, sizeof(uint32_t), + (const uint8_t *) &user_info_type); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + goto done; + } + break; +/* TODO: we need the pam session cookie here to make sure that cached + * authentication was successful */ + case SSS_PAM_PREAUTH: + case SSS_PAM_SETCRED: + case SSS_PAM_ACCT_MGMT: + case SSS_PAM_OPEN_SESSION: + case SSS_PAM_CLOSE_SESSION: + DEBUG(SSSDBG_OP_FAILURE, + "Assuming offline authentication setting status for " + "pam call %d to PAM_SUCCESS.\n", pd->cmd); + pd->pam_status = PAM_SUCCESS; + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unknown PAM call [%d].\n", pd->cmd); + pd->pam_status = PAM_MODULE_UNKNOWN; + } + } + + if (pd->pam_status == PAM_SUCCESS && pd->cmd == SSS_PAM_CHAUTHTOK) { + ret = pam_null_last_online_auth_with_curr_token(preq->domain, + pd->user); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_null_last_online_auth_with_curr_token failed: " + "%s [%d].\n", sss_strerror(ret), ret); + goto done; + } + } + + if (pd->response_delay > 0) { + ret = gettimeofday(&tv, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "gettimeofday failed [%d][%s].\n", + errno, strerror(errno)); + goto done; + } + tv.tv_sec += pd->response_delay; + tv.tv_usec = 0; + pd->response_delay = 0; + + te = tevent_add_timer(cctx->ev, cctx, tv, pam_reply_delay, preq); + if (te == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to add event pam_reply_delay.\n"); + goto done; + } + + return; + } + + /* If this was a successful login, save the lastLogin time */ + if (pd->cmd == SSS_PAM_AUTHENTICATE && + pd->pam_status == PAM_SUCCESS && + preq->domain && + preq->domain->cache_credentials && + !pd->offline_auth && + !pd->last_auth_saved && + !is_files_provider(preq->domain)) { + ret = set_last_login(preq); + if (ret != EOK) { + goto done; + } + return; + } + + ret = sss_packet_new(prctx->creq, 0, sss_packet_get_cmd(prctx->creq->in), + &prctx->creq->out); + if (ret != EOK) { + goto done; + } + + /* Passkey auth user notification if no TGT is granted */ + if (pd->cmd == SSS_PAM_AUTHENTICATE && + pd->pam_status == PAM_SUCCESS && + preq->pd->passkey_local_done) { + user_info_type = SSS_PAM_USER_INFO_NO_KRB_TGT; + pam_add_response(pd, SSS_PAM_USER_INFO, + sizeof(uint32_t), (const uint8_t *) &user_info_type); + DEBUG(SSSDBG_IMPORTANT_INFO, + "User [%s] logged in with local passkey authentication, single " + "sign on ticket is not obtained.\n", pd->user); + } + + /* Account expiration warning is printed for sshd. If pam_verbosity + * is equal or above PAM_VERBOSITY_INFO then all services are informed + * about account expiration. + */ + if (pd->pam_status == PAM_ACCT_EXPIRED && + ((pd->service != NULL && strcasecmp(pd->service, "sshd") == 0) || + pam_verbosity >= PAM_VERBOSITY_INFO)) { + + ret = confdb_get_string(pctx->rctx->cdb, pd, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_ACCOUNT_EXPIRED_MESSAGE, "", + &pam_account_expired_message); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to get expiration message: %d:[%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + inform_user(pd, pam_account_expired_message); + } + + if (pd->account_locked) { + + ret = confdb_get_string(pctx->rctx->cdb, pd, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_ACCOUNT_LOCKED_MESSAGE, "", + &pam_account_locked_message); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Failed to get expiration message: %d:[%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + inform_user(pd, pam_account_locked_message); + } + + ret = filter_responses(pctx, pd->resp_list, pd); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "filter_responses failed, not fatal.\n"); + } + + if (pd->domain != NULL) { + ret = pam_add_response(pd, SSS_PAM_DOMAIN_NAME, strlen(pd->domain)+1, + (uint8_t *) pd->domain); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + goto done; + } + } + + if (pd->cmd == SSS_PAM_PREAUTH) { + ret = pam_eval_prompting_config(pctx, pd); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to add prompting information, " + "using defaults.\n"); + } + +#ifdef BUILD_PASSKEY + ret = pam_eval_passkey_response(pctx, pd, preq, &pk_preauth_done); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to eval passkey response\n"); + goto done; + } + + if (may_do_passkey_auth(pctx, pd) + && !pk_preauth_done + && preq->passkey_data_exists + && local_passkey_auth_allow) { + ret = passkey_local(cctx, cctx->ev, pctx, preq, pd); + pam_check_user_done(preq, ret); + return; + } +#endif /* BUILD_PASSKEY */ + } + + /* + * Export non-overridden shell to tlog-rec-session when opening the session + */ + if (pd->cmd == SSS_PAM_OPEN_SESSION && pd->pam_status == PAM_SUCCESS) { + ret = pam_reply_sr_export_shell(preq, "TLOG_REC_SESSION_SHELL"); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "failed to export the shell to tlog-rec-session.\n"); + goto done; + } + } + + resp_c = 0; + resp_size = 0; + resp = pd->resp_list; + while(resp != NULL) { + if (!resp->do_not_send_to_client) { + resp_c++; + resp_size += resp->len; + } + resp = resp->next; + } + + ret = sss_packet_grow(prctx->creq->out, sizeof(int32_t) + + sizeof(int32_t) + + resp_c * 2* sizeof(int32_t) + + resp_size); + if (ret != EOK) { + goto done; + } + + sss_packet_get_body(prctx->creq->out, &body, &blen); + DEBUG(SSSDBG_FUNC_DATA, "blen: %zu\n", blen); + p = 0; + + memcpy(&body[p], &pd->pam_status, sizeof(int32_t)); + p += sizeof(int32_t); + + memcpy(&body[p], &resp_c, sizeof(int32_t)); + p += sizeof(int32_t); + + resp = pd->resp_list; + while(resp != NULL) { + if (!resp->do_not_send_to_client) { + memcpy(&body[p], &resp->type, sizeof(int32_t)); + p += sizeof(int32_t); + memcpy(&body[p], &resp->len, sizeof(int32_t)); + p += sizeof(int32_t); + memcpy(&body[p], resp->data, resp->len); + p += resp->len; + } + + resp = resp->next; + } + +done: + DEBUG(SSSDBG_FUNC_DATA, "Returning [%d]: %s to the client\n", + pd->pam_status, pam_strerror(NULL, pd->pam_status)); + sss_cmd_done(cctx, preq); +} + +static void pam_dom_forwarder(struct pam_auth_req *preq); + +static void pam_handle_cached_login(struct pam_auth_req *preq, int ret, + time_t expire_date, time_t delayed_until, + bool use_cached_auth) +{ + uint32_t resp_type; + size_t resp_len; + uint8_t *resp; + int64_t dummy; + + preq->pd->pam_status = cached_login_pam_status(ret); + + switch (preq->pd->pam_status) { + case PAM_SUCCESS: + resp_type = SSS_PAM_USER_INFO_OFFLINE_AUTH; + resp_len = sizeof(uint32_t) + sizeof(int64_t); + resp = talloc_size(preq->pd, resp_len); + if (resp == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "talloc_size failed, cannot prepare user info.\n"); + } else { + memcpy(resp, &resp_type, sizeof(uint32_t)); + dummy = (int64_t) expire_date; + memcpy(resp+sizeof(uint32_t), &dummy, sizeof(int64_t)); + ret = pam_add_response(preq->pd, SSS_PAM_USER_INFO, resp_len, + (const uint8_t *) resp); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + } + } + break; + case PAM_PERM_DENIED: + if (delayed_until >= 0) { + resp_type = SSS_PAM_USER_INFO_OFFLINE_AUTH_DELAYED; + resp_len = sizeof(uint32_t) + sizeof(int64_t); + resp = talloc_size(preq->pd, resp_len); + if (resp == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "talloc_size failed, cannot prepare user info.\n"); + } else { + memcpy(resp, &resp_type, sizeof(uint32_t)); + dummy = (int64_t) delayed_until; + memcpy(resp+sizeof(uint32_t), &dummy, sizeof(int64_t)); + ret = pam_add_response(preq->pd, SSS_PAM_USER_INFO, resp_len, + (const uint8_t *) resp); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "pam_add_response failed.\n"); + } + } + } + break; + case PAM_AUTH_ERR: + /* Was this attempt to authenticate from cache? */ + if (use_cached_auth) { + /* Don't try cached authentication again, try online check. */ + DEBUG(SSSDBG_FUNC_DATA, + "Cached authentication failed for: %s\n", + preq->pd->user); + preq->cached_auth_failed = true; + pam_dom_forwarder(preq); + return; + } + break; + default: + DEBUG(SSSDBG_TRACE_LIBS, + "cached login returned: %d\n", preq->pd->pam_status); + } + + pam_reply(preq); + return; +} + +static void pam_forwarder_cb(struct tevent_req *req); +static void pam_forwarder_cert_cb(struct tevent_req *req); +int pam_check_user_search(struct pam_auth_req *preq); + + +/* TODO: we should probably return some sort of cookie that is set in the + * PAM_ENVIRONMENT, so that we can save performing some calls and cache + * data. */ + +static errno_t pam_forwarder_parse_data(struct cli_ctx *cctx, struct pam_data *pd) +{ + struct cli_protocol *prctx; + uint8_t *body; + size_t blen; + errno_t ret; + uint32_t terminator; + const char *key_id; + + prctx = talloc_get_type(cctx->protocol_ctx, struct cli_protocol); + + sss_packet_get_body(prctx->creq->in, &body, &blen); + if (blen >= sizeof(uint32_t)) { + SAFEALIGN_COPY_UINT32(&terminator, + body + blen - sizeof(uint32_t), + NULL); + if (terminator != SSS_END_OF_PAM_REQUEST) { + DEBUG(SSSDBG_CRIT_FAILURE, "Received data not terminated.\n"); + ret = EINVAL; + goto done; + } + } + + switch (prctx->cli_protocol_version->version) { + case 1: + ret = pam_parse_in_data(pd, body, blen); + break; + case 2: + ret = pam_parse_in_data_v2(pd, body, blen); + break; + case 3: + ret = pam_parse_in_data_v3(pd, body, blen); + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Illegal protocol version [%d].\n", + prctx->cli_protocol_version->version); + ret = EINVAL; + } + if (ret != EOK) { + goto done; + } + + if (pd->logon_name != NULL) { + ret = sss_parse_name_for_domains(pd, cctx->rctx->domains, + cctx->rctx->default_domain, + pd->logon_name, + &pd->domain, &pd->user); + } else { + /* SSS_PAM_PREAUTH request may have a missing name, e.g. if the + * name is determined with the help of a certificate. During + * SSS_PAM_AUTHENTICATE at least a key ID is needed to identify the + * selected certificate. */ + if (pd->cmd == SSS_PAM_AUTHENTICATE + && may_do_cert_auth(talloc_get_type(cctx->rctx->pvt_ctx, + struct pam_ctx), pd) + && (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_SC_PIN + || sss_authtok_get_type(pd->authtok) + == SSS_AUTHTOK_TYPE_SC_KEYPAD)) { + ret = sss_authtok_get_sc(pd->authtok, NULL, NULL, NULL, NULL, NULL, + NULL, &key_id, NULL, NULL, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_sc failed.\n"); + goto done; + } + + if (key_id == NULL || *key_id == '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, + "Missing logon and Smartcard key ID during " + "authentication.\n"); + ret = ERR_NO_CREDS; + goto done; + } + + ret = EOK; + } else if (pd->cmd == SSS_PAM_PREAUTH + && may_do_cert_auth(talloc_get_type(cctx->rctx->pvt_ctx, + struct pam_ctx), pd)) { + ret = EOK; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing logon name in PAM request.\n"); + ret = ERR_NO_CREDS; + goto done; + } + } + + DEBUG_PAM_DATA(SSSDBG_CONF_SETTINGS, pd); + +done: + return ret; +} + +static bool is_uid_trusted(struct cli_creds *creds, + size_t trusted_uids_count, + uid_t *trusted_uids) +{ + errno_t ret; + + /* root is always trusted */ + if (client_euid(creds) == 0) { + return true; + } + + /* All uids are allowed */ + if (trusted_uids_count == 0) { + return true; + } + + ret = check_allowed_uids(client_euid(creds), trusted_uids_count, trusted_uids); + if (ret == EOK) return true; + + return false; +} + +static bool is_domain_public(char *name, + char **public_dom_names, + size_t public_dom_names_count) +{ + size_t i; + + for(i=0; i < public_dom_names_count; i++) { + if (strcasecmp(name, public_dom_names[i]) == 0) { + return true; + } + } + return false; +} + +static enum cache_req_dom_type +get_domain_request_type(struct pam_auth_req *preq, + struct pam_ctx *pctx) +{ + enum cache_req_dom_type req_dom_type; + + /* By default, only POSIX domains are to be contacted */ + req_dom_type = CACHE_REQ_POSIX_DOM; + + for (int i = 0; pctx->app_services[i]; i++) { + if (strcmp(pctx->app_services[i], preq->pd->service) == 0) { + req_dom_type = CACHE_REQ_APPLICATION_DOM; + break; + } + } + + return req_dom_type; +} + +static errno_t check_cert(TALLOC_CTX *mctx, + struct tevent_context *ev, + struct pam_ctx *pctx, + struct pam_auth_req *preq, + struct pam_data *pd) +{ + int p11_child_timeout; + int wait_for_card_timeout; + char *cert_verification_opts; + errno_t ret; + struct tevent_req *req; + char *uri = NULL; + + ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_P11_CHILD_TIMEOUT, + P11_CHILD_TIMEOUT_DEFAULT, + &p11_child_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read p11_child_timeout from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + if ((pd->cli_flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH) && pd->priv == 1) { + ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_WAIT_FOR_CARD_TIMEOUT, + P11_WAIT_FOR_CARD_TIMEOUT_DEFAULT, + &wait_for_card_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read [%s] from confdb: [%d]: %s\n", + CONFDB_PAM_WAIT_FOR_CARD_TIMEOUT, ret, sss_strerror(ret)); + return ret; + } + + p11_child_timeout += wait_for_card_timeout; + } + + ret = confdb_get_string(pctx->rctx->cdb, mctx, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_CERT_VERIFICATION, + NULL, &cert_verification_opts); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read '"CONFDB_PAM_CERT_VERIFICATION"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + if (cert_verification_opts == NULL) { + ret = confdb_get_string(pctx->rctx->cdb, mctx, CONFDB_MONITOR_CONF_ENTRY, + CONFDB_MONITOR_CERT_VERIFICATION, NULL, + &cert_verification_opts); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read '"CONFDB_MONITOR_CERT_VERIFICATION"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + } + + ret = confdb_get_string(pctx->rctx->cdb, mctx, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_P11_URI, NULL, &uri); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read '"CONFDB_PAM_P11_URI"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + req = pam_check_cert_send(mctx, ev, + pctx->ca_db, p11_child_timeout, + cert_verification_opts, pctx->sss_certmap_ctx, + uri, pd); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "pam_check_cert_send failed.\n"); + return ENOMEM; + } + + tevent_req_set_callback(req, pam_forwarder_cert_cb, preq); + return EAGAIN; +} + + +static int pam_forwarder(struct cli_ctx *cctx, int pam_cmd) +{ + struct pam_auth_req *preq; + struct pam_data *pd; + int ret; + struct pam_ctx *pctx = + talloc_get_type(cctx->rctx->pvt_ctx, struct pam_ctx); + struct tevent_req *req; + + preq = talloc_zero(cctx, struct pam_auth_req); + if (!preq) { + return ENOMEM; + } + preq->cctx = cctx; + preq->cert_auth_local = false; + preq->client_id_num = cctx->client_id_num; + + preq->pd = create_pam_data(preq); + if (!preq->pd) { + talloc_free(preq); + return ENOMEM; + } + pd = preq->pd; + + preq->is_uid_trusted = is_uid_trusted(cctx->creds, + pctx->trusted_uids_count, + pctx->trusted_uids); + + if (!preq->is_uid_trusted) { + DEBUG(SSSDBG_MINOR_FAILURE, "uid %"SPRIuid" is not trusted.\n", + client_euid(cctx->creds)); + } + + + pd->cmd = pam_cmd; + pd->priv = cctx->priv; + pd->client_id_num = cctx->client_id_num; + + ret = pam_forwarder_parse_data(cctx, pd); + if (ret == EAGAIN) { + req = sss_dp_get_domains_send(cctx->rctx, cctx->rctx, true, pd->domain); + if (req == NULL) { + ret = ENOMEM; + } else { + tevent_req_set_callback(req, pam_forwarder_cb, preq); + ret = EAGAIN; + } + goto done; + } else if (ret != EOK) { + goto done; + } + + /* Determine what domain type to contact */ + preq->req_dom_type = get_domain_request_type(preq, pctx); + + if (pd->cmd == SSS_PAM_AUTHENTICATE + && (pd->cli_flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH) + && !IS_SC_AUTHTOK(pd->authtok)) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Smartcard authentication required but authentication " + "token [%d][%s] is not suitable.\n", + sss_authtok_get_type(pd->authtok), + sss_authtok_type_to_str(sss_authtok_get_type(pd->authtok))); + ret = ERR_NO_CREDS; + goto done; + } + + /* Try backend first for authentication before doing local Smartcard + * authentication if a logon name is available. Otherwise try to derive + * the logon name from the certificate first. */ + if ((pd->cmd != SSS_PAM_AUTHENTICATE + || (pd->cmd == SSS_PAM_AUTHENTICATE && pd->logon_name == NULL)) + && may_do_cert_auth(pctx, pd)) { + ret = check_cert(cctx, cctx->ev, pctx, preq, pd); + /* Finish here */ + goto done; + } + + /* This is set to false inside passkey_local() if no passkey data is found. + * It is checked in pam_reply() to avoid an endless loop */ + preq->passkey_data_exists = true; + +#ifdef BUILD_PASSKEY + if ((pd->cmd == SSS_PAM_AUTHENTICATE)) { + if (may_do_passkey_auth(pctx, pd)) { + if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY_KRB) { + ret = passkey_kerberos(pctx, preq->pd, preq); + goto done; + } else if ((sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY) || + (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_EMPTY)) { + ret = passkey_local(cctx, cctx->ev, pctx, preq, pd); + goto done; + } + } + } +#endif /* BUILD_PASSKEY */ + + ret = pam_check_user_search(preq); + +done: + return pam_check_user_done(preq, ret); +} + +static errno_t pam_user_by_cert_step(struct pam_auth_req *preq); +static void pam_forwarder_lookup_by_cert_done(struct tevent_req *req); +static void pam_forwarder_cert_cb(struct tevent_req *req) +{ + struct pam_auth_req *preq = tevent_req_callback_data(req, + struct pam_auth_req); + struct pam_data *pd; + errno_t ret = EOK; + const char *cert; + + ret = pam_check_cert_recv(req, preq, &preq->cert_list); + talloc_free(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_cert request failed.\n"); + goto done; + } + + pd = preq->pd; + + cert = sss_cai_get_cert(preq->cert_list); + + if (cert == NULL) { + if (pd->logon_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "No certificate found and no logon name given, " \ + "authentication not possible.\n"); + ret = ENOENT; + } else if (pd->cmd == SSS_PAM_PREAUTH + && (pd->cli_flags & PAM_CLI_FLAGS_TRY_CERT_AUTH)) { + DEBUG(SSSDBG_TRACE_ALL, + "try_cert_auth flag set but no certificate available, " + "request finished.\n"); + preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + pam_reply(preq); + return; + } else { + if (pd->cmd == SSS_PAM_AUTHENTICATE) { + DEBUG(SSSDBG_CRIT_FAILURE, + "No certificate returned, authentication failed.\n"); + preq->pd->pam_status = PAM_AUTH_ERR; + pam_reply(preq); + return; + } else { + ret = pam_check_user_search(preq); + } + + } + goto done; + } + + preq->current_cert = preq->cert_list; + ret = pam_user_by_cert_step(preq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "pam_user_by_cert_step failed.\n"); + goto done; + } + + return; + +done: + pam_check_user_done(preq, ret); +} + +static errno_t pam_user_by_cert_step(struct pam_auth_req *preq) +{ + struct cli_ctx *cctx = preq->cctx; + struct tevent_req *req; + struct pam_ctx *pctx = + talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); + + if (preq->current_cert == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing certificate data.\n"); + return EINVAL; + } + + req = cache_req_user_by_cert_send(preq, cctx->ev, cctx->rctx, + pctx->rctx->ncache, 0, + preq->req_dom_type, NULL, + sss_cai_get_cert(preq->current_cert)); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "cache_req_user_by_cert_send failed.\n"); + return ENOMEM; + } + + tevent_req_set_callback(req, pam_forwarder_lookup_by_cert_done, preq); + return EOK; +} + +static errno_t get_results_from_all_domains(TALLOC_CTX *mem_ctx, + struct cache_req_result **results, + struct ldb_result **ldb_results) +{ + int ret; + size_t count = 0; + size_t c; + size_t d; + size_t r = 0; + struct ldb_result *res; + + for (d = 0; results != NULL && results[d] != NULL; d++) { + count += results[d]->count; + } + + res = talloc_zero(mem_ctx, struct ldb_result); + if (res == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n"); + return ENOMEM; + } + + if (count == 0) { + *ldb_results = res; + return EOK; + } + + res->msgs = talloc_zero_array(res, struct ldb_message *, count); + if (res->msgs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + return ENOMEM; + } + res->count = count; + + for (d = 0; results != NULL && results[d] != NULL; d++) { + for (c = 0; c < results[d]->count; c++) { + if (r >= count) { + DEBUG(SSSDBG_CRIT_FAILURE, + "More results found then counted before.\n"); + ret = EINVAL; + goto done; + } + res->msgs[r++] = talloc_steal(res->msgs, results[d]->msgs[c]); + } + } + + *ldb_results = res; + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(res); + } + + return ret; +} + +/* Return true if hint is set for at least one domain */ +static bool get_user_name_hint(struct sss_domain_info *domains) +{ + struct sss_domain_info *d; + + DLIST_FOR_EACH(d, domains) { + if (d->user_name_hint == true) { + return true; + } + } + + return false; +} + +static void pam_forwarder_lookup_by_cert_done(struct tevent_req *req) +{ + int ret; + struct cache_req_result **results; + struct pam_auth_req *preq = tevent_req_callback_data(req, + struct pam_auth_req); + const char *cert_user = NULL; + size_t cert_count = 0; + size_t cert_user_count = 0; + struct ldb_result *cert_user_objs; + + ret = cache_req_recv(preq, req, &results); + talloc_zfree(req); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "cache_req_user_by_cert request failed.\n"); + goto done; + } + + if (ret == EOK) { + ret = get_results_from_all_domains(preq, results, + &cert_user_objs); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "get_results_from_all_domains failed.\n"); + goto done; + } + + sss_cai_set_cert_user_objs(preq->current_cert, cert_user_objs); + } + + preq->current_cert = sss_cai_get_next(preq->current_cert); + if (preq->current_cert != NULL) { + ret = pam_user_by_cert_step(preq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "pam_user_by_cert_step failed.\n"); + goto done; + } + return; + } + + sss_cai_check_users(&preq->cert_list, &cert_count, &cert_user_count); + DEBUG(SSSDBG_TRACE_ALL, + "Found [%zu] certificates and [%zu] related users.\n", + cert_count, cert_user_count); + + if (cert_user_count == 0) { + if (preq->pd->logon_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Missing logon name and no certificate user found.\n"); + ret = ENOENT; + goto done; + } + } else { + + if (preq->pd->logon_name == NULL) { + if (preq->pd->cmd != SSS_PAM_PREAUTH + && preq->pd->cmd != SSS_PAM_AUTHENTICATE) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Missing logon name only allowed during (pre-)auth.\n"); + ret = ENOENT; + goto done; + } + + if (cert_count > 1) { + for (preq->current_cert = preq->cert_list; + preq->current_cert != NULL; + preq->current_cert = sss_cai_get_next(preq->current_cert)) { + + ret = add_pam_cert_response(preq->pd, + preq->cctx->rctx->domains, "", + preq->current_cert, + get_user_name_hint(preq->cctx->rctx->domains) + ? SSS_PAM_CERT_INFO_WITH_HINT + : SSS_PAM_CERT_INFO); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "add_pam_cert_response failed.\n"); + preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + } + } + + ret = EOK; + preq->pd->pam_status = PAM_SUCCESS; + pam_reply(preq); + goto done; + } + + if (cert_user_count == 1) { + cert_user_objs = sss_cai_get_cert_user_objs(preq->cert_list); + if (cert_user_objs == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing certificate user.\n"); + ret = ENOENT; + goto done; + } + + cert_user = ldb_msg_find_attr_as_string( + cert_user_objs->msgs[0], + SYSDB_NAME, NULL); + if (cert_user == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Certificate user object has not name.\n"); + ret = ENOENT; + goto done; + } + + DEBUG(SSSDBG_FUNC_DATA, + "Found certificate user [%s].\n", cert_user); + + ret = sss_parse_name_for_domains(preq->pd, + preq->cctx->rctx->domains, + preq->cctx->rctx->default_domain, + cert_user, + &preq->pd->domain, + &preq->pd->user); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_parse_name_for_domains failed.\n"); + goto done; + } + } + + if (get_user_name_hint(preq->cctx->rctx->domains) + && preq->pd->cmd == SSS_PAM_PREAUTH) { + ret = add_pam_cert_response(preq->pd, + preq->cctx->rctx->domains, cert_user, + preq->cert_list, + SSS_PAM_CERT_INFO_WITH_HINT); + preq->pd->pam_status = PAM_SUCCESS; + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "add_pam_cert_response failed.\n"); + preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + } + ret = EOK; + pam_reply(preq); + goto done; + } + + /* Without user name hints the certificate must map to single user + * if no login name was given */ + if (cert_user == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "More than one user mapped to certificate.\n"); + ret = ERR_NO_CREDS; + goto done; + } + + /* If logon_name was not given during authentication add a + * SSS_PAM_CERT_INFO message to send the name to the caller. */ + if (preq->pd->cmd == SSS_PAM_AUTHENTICATE + && preq->pd->logon_name == NULL) { + ret = add_pam_cert_response(preq->pd, + preq->cctx->rctx->domains, cert_user, + preq->cert_list, + SSS_PAM_CERT_INFO); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "add_pam_cert_response failed.\n"); + preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + goto done; + } + } + + /* cert_user will be returned to the PAM client as user name, so + * we can use it here already e.g. to set in initgroups timeout */ + preq->pd->logon_name = talloc_strdup(preq->pd, cert_user); + if (preq->pd->logon_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + } + } + + if (preq->user_obj == NULL) { + ret = pam_check_user_search(preq); + } else { + ret = EOK; + } + + if (ret == EOK) { + pam_dom_forwarder(preq); + } + +done: + pam_check_user_done(preq, ret); +} + +static void pam_forwarder_cb(struct tevent_req *req) +{ + struct pam_auth_req *preq = tevent_req_callback_data(req, + struct pam_auth_req); + struct cli_ctx *cctx = preq->cctx; + struct pam_data *pd; + errno_t ret = EOK; + struct pam_ctx *pctx = + talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); + + ret = sss_dp_get_domains_recv(req); + talloc_free(req); + if (ret != EOK) { + goto done; + } + + ret = p11_refresh_certmap_ctx(pctx, pctx->rctx->domains); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "p11_refresh_certmap_ctx failed, " + "certificate matching might not work as expected"); + } + + pd = preq->pd; + + ret = pam_forwarder_parse_data(cctx, pd); + if (ret == EAGAIN) { + DEBUG(SSSDBG_TRACE_FUNC, "Assuming %s is a UPN\n", pd->logon_name); + /* If not, cache_req will error out later */ + pd->user = talloc_strdup(pd, pd->logon_name); + if (pd->user == NULL) { + ret = ENOMEM; + goto done; + } + pd->domain = NULL; + } else if (ret != EOK) { + ret = EINVAL; + goto done; + } + + /* try backend first for authentication before doing local Smartcard + * authentication */ + if (pd->cmd != SSS_PAM_AUTHENTICATE && may_do_cert_auth(pctx, pd)) { + ret = check_cert(cctx, cctx->ev, pctx, preq, pd); + /* Finish here */ + goto done; + } + +#ifdef BUILD_PASSKEY + /* This is set to false inside passkey_local() if no passkey data is found. + * It is checked in pam_reply() to avoid an endless loop */ + preq->passkey_data_exists = true; + + if ((pd->cmd == SSS_PAM_AUTHENTICATE)) { + if (may_do_passkey_auth(pctx, pd)) { + if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY_KRB) { + ret = passkey_kerberos(pctx, preq->pd, preq); + goto done; + } else if ((sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY) || + (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_EMPTY)) { + ret = passkey_local(cctx, cctx->ev, pctx, preq, pd); + goto done; + } + } + } +#endif /* BUILD_PASSKEY */ + + ret = pam_check_user_search(preq); + +done: + pam_check_user_done(preq, ret); +} + +static void pam_check_user_search_next(struct tevent_req *req); +static void pam_check_user_search_lookup(struct tevent_req *req); +static void pam_check_user_search_done(struct pam_auth_req *preq, int ret, + struct cache_req_result *result); + +/* lookup the user uid from the cache first, + * then we'll refresh initgroups if needed */ +int pam_check_user_search(struct pam_auth_req *preq) +{ + struct tevent_req *dpreq; + struct cache_req_data *data; + + data = cache_req_data_name(preq, + CACHE_REQ_INITGROUPS, + preq->pd->logon_name); + if (data == NULL) { + return ENOMEM; + } + + cache_req_data_set_bypass_cache(data, false); + cache_req_data_set_bypass_dp(data, true); + cache_req_data_set_requested_domains(data, preq->pd->requested_domains); + + dpreq = cache_req_send(preq, + preq->cctx->rctx->ev, + preq->cctx->rctx, + preq->cctx->rctx->ncache, + 0, + preq->req_dom_type, + NULL, + data); + if (!dpreq) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Out of memory sending data provider request\n"); + return ENOMEM; + } + + tevent_req_set_callback(dpreq, pam_check_user_search_next, preq); + + /* tell caller we are in an async call */ + return EAGAIN; +} + +static void pam_check_user_search_next(struct tevent_req *req) +{ + struct pam_auth_req *preq; + struct pam_ctx *pctx; + struct cache_req_result *result = NULL; + struct cache_req_data *data; + struct tevent_req *dpreq; + int ret; + + preq = tevent_req_callback_data(req, struct pam_auth_req); + pctx = talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); + + ret = cache_req_single_domain_recv(preq, req, &result); + talloc_zfree(req); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "Cache lookup failed, trying to get fresh " + "data from the backend.\n"); + } + + DEBUG(SSSDBG_TRACE_ALL, "PAM initgroups scheme [%s].\n", + pam_initgroup_enum_to_string(pctx->initgroups_scheme)); + + if (ret == EOK) { + bool user_has_session = false; + + if (pctx->initgroups_scheme == PAM_INITGR_NO_SESSION) { + uid_t uid = ldb_msg_find_attr_as_uint64(result->msgs[0], + SYSDB_UIDNUM, 0); + if (!uid) { + DEBUG(SSSDBG_CRIT_FAILURE, "A user with no UID?\n"); + talloc_zfree(preq->cctx); + return; + } + + /* If a user already has a session on the system, we take the + * cache for granted and do not force an online lookup. This is + * because in most cases the user is just trying to authenticate + * but not create a new session (sudo, lockscreen, polkit, etc.) + * An online refresh in this situation would just delay operations + * without providing any useful additional information. + */ + (void)check_if_uid_is_active(uid, &user_has_session); + + DEBUG(SSSDBG_TRACE_ALL, "Found %s session for uid %"SPRIuid".\n", + user_has_session ? "a" : "no", uid); + } + + /* The initgr cache is used to make sure that during a single PAM + * session (auth, acct_mgtm, ....) the backend is contacted only + * once. logon_name is the name provided by the PAM client and + * will not be modified during the request, so it makes sense to + * use it here instead od the pd->user. + */ + ret = pam_initgr_check_timeout(pctx->id_table, preq->pd->logon_name); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "Could not look up initgroup timeout\n"); + } + + if ((ret == EOK) || user_has_session + || pctx->initgroups_scheme == PAM_INITGR_NEVER) { + DEBUG(SSSDBG_TRACE_ALL, "No new initgroups needed because:\n"); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_ALL, "PAM initgr cache still valid.\n"); + } else if (user_has_session) { + DEBUG(SSSDBG_TRACE_ALL, "there is a active session for " + "user [%s].\n", preq->pd->logon_name); + } else if (pctx->initgroups_scheme == PAM_INITGR_NEVER) { + DEBUG(SSSDBG_TRACE_ALL, "initgroups scheme is 'never'.\n"); + } + pam_check_user_search_done(preq, EOK, result); + return; + } + } + + /* If we get here it means the user was not found or does not have a + * session, or initgr has not been cached before, so we force a new + * online lookup */ + data = cache_req_data_name(preq, + CACHE_REQ_INITGROUPS, + preq->pd->logon_name); + if (data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory\n"); + talloc_zfree(preq->cctx); + return; + } + cache_req_data_set_bypass_cache(data, true); + cache_req_data_set_bypass_dp(data, false); + cache_req_data_set_requested_domains(data, preq->pd->requested_domains); + + dpreq = cache_req_send(preq, + preq->cctx->rctx->ev, + preq->cctx->rctx, + preq->cctx->rctx->ncache, + 0, + preq->req_dom_type, + NULL, + data); + if (!dpreq) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Out of memory sending data provider request\n"); + talloc_zfree(preq->cctx); + return; + } + + tevent_req_set_callback(dpreq, pam_check_user_search_lookup, preq); +} + +static void pam_check_user_search_lookup(struct tevent_req *req) +{ + struct cache_req_result *result; + struct pam_auth_req *preq; + int ret; + + preq = tevent_req_callback_data(req, struct pam_auth_req); + + ret = cache_req_single_domain_recv(preq, req, &result); + talloc_zfree(req); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Fatal error, killing connection!\n"); + talloc_zfree(preq->cctx); + return; + } + + pam_check_user_search_done(preq, ret, result); +} + +static void pam_check_user_search_done(struct pam_auth_req *preq, int ret, + struct cache_req_result *result) +{ + struct pam_ctx *pctx; + + pctx = talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); + + if (ret == EOK) { + preq->user_obj = result->msgs[0]; + pd_set_primary_name(preq->user_obj, preq->pd); + preq->domain = result->domain; + + ret = pam_initgr_cache_set(pctx->rctx->ev, + pctx->id_table, + preq->pd->logon_name, + pctx->id_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Could not save initgr timestamp." + "Proceeding with PAM actions\n"); + } + + pam_dom_forwarder(preq); + } + + ret = pam_check_user_done(preq, ret); + if (ret != EOK) { + preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(preq); + } +} + +int pam_check_user_done(struct pam_auth_req *preq, int ret) +{ + switch (ret) { + case EOK: + break; + + case EAGAIN: + /* performing async request, just return */ + break; + + case ENOENT: + preq->pd->pam_status = PAM_USER_UNKNOWN; + pam_reply(preq); + break; + + case ERR_P11_PIN_LOCKED: + preq->pd->pam_status = PAM_AUTH_ERR; + pam_reply(preq); + break; + + case ERR_NO_CREDS: + preq->pd->pam_status = PAM_CRED_INSUFFICIENT; + pam_reply(preq); + break; + + default: + preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(preq); + break; + } + + return EOK; +} + +static errno_t pam_is_last_online_login_fresh(struct sss_domain_info *domain, + const char* user, + int cached_auth_timeout, + bool *_result) +{ + errno_t ret; + bool result = true; + uint64_t last_login; + + ret = pam_get_last_online_auth_with_curr_token(domain, user, &last_login); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "sysdb_get_last_online_auth_with_curr_token failed: %s:[%d]\n", + sss_strerror(ret), ret); + goto done; + } + + result = time(NULL) < (last_login + cached_auth_timeout); + ret = EOK; + +done: + if (ret == EOK) { + *_result = result; + } + return ret; +} + +static bool pam_is_authtok_cachable(struct sss_auth_token *authtok) +{ + enum sss_authtok_type type; + bool cachable = false; + + type = sss_authtok_get_type(authtok); + if (type == SSS_AUTHTOK_TYPE_PASSWORD) { + cachable = true; + } else { + DEBUG(SSSDBG_TRACE_LIBS, "Authentication token can't be cached\n"); + } + + return cachable; +} + +static bool pam_can_user_cache_auth(struct sss_domain_info *domain, + int pam_cmd, + struct sss_auth_token *authtok, + const char* user, + bool cached_auth_failed) +{ + errno_t ret; + bool result = false; + + if (cached_auth_failed) { + /* Do not retry indefinitely */ + return false; + } + + if (!domain->cache_credentials || domain->cached_auth_timeout <= 0) { + return false; + } + + if (pam_cmd == SSS_PAM_PREAUTH + || (pam_cmd == SSS_PAM_AUTHENTICATE + && pam_is_authtok_cachable(authtok))) { + + ret = pam_is_last_online_login_fresh(domain, user, + domain->cached_auth_timeout, + &result); + if (ret != EOK) { + /* non-critical, consider fail as 'non-fresh value' */ + DEBUG(SSSDBG_MINOR_FAILURE, + "pam_is_last_online_login_fresh failed: %s:[%d]\n", + sss_strerror(ret), ret); + } + } + + return result; +} + +static void pam_dom_forwarder(struct pam_auth_req *preq) +{ + TALLOC_CTX *tmp_ctx = NULL; + int ret; + struct pam_ctx *pctx = + talloc_get_type(preq->cctx->rctx->pvt_ctx, struct pam_ctx); + const char *cert_user; + struct ldb_result *cert_user_objs; + bool sc_auth; + bool passkey_auth; + size_t c; + char *local_policy = NULL; + bool found = false; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return; + } + + if (!preq->pd->domain) { + preq->pd->domain = preq->domain->name; + } + + /* Untrusted users can access only public domains. */ + if (!preq->is_uid_trusted && + !is_domain_public(preq->pd->domain, pctx->public_domains, + pctx->public_domains_count)) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Untrusted user %"SPRIuid" cannot access non-public domain %s.\n", + client_euid(preq->cctx->creds), preq->pd->domain); + preq->pd->pam_status = PAM_PERM_DENIED; + pam_reply(preq); + return; + } + + /* skip this domain if not requested and the user is trusted + * as untrusted users can't request a domain */ + if (preq->is_uid_trusted && + !is_domain_requested(preq->pd, preq->pd->domain)) { + preq->pd->pam_status = PAM_USER_UNKNOWN; + pam_reply(preq); + return; + } + + if (pam_can_user_cache_auth(preq->domain, + preq->pd->cmd, + preq->pd->authtok, + preq->pd->user, + preq->cached_auth_failed)) { + preq->use_cached_auth = true; + pam_reply(preq); + return; + } + + /* Skip online auth when local auth policy = only */ +#ifdef BUILD_PASSKEY + if (may_do_cert_auth(pctx, preq->pd) || may_do_passkey_auth(pctx, preq->pd)) { +#else + if (may_do_cert_auth(pctx, preq->pd)) { +#endif /* BUILD_PASSKEY */ + if (preq->domain->name != NULL) { + ret = pam_eval_local_auth_policy(preq->cctx, pctx, preq->pd, preq, + &sc_auth, + &passkey_auth, + &local_policy); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to evaluate local auth policy\n"); + preq->pd->pam_status = PAM_AUTH_ERR; + pam_reply(preq); + return; + } + } + } + + if (may_do_cert_auth(pctx, preq->pd) && preq->cert_list != NULL) { + /* Check if user matches certificate user */ + found = false; + for (preq->current_cert = preq->cert_list; + preq->current_cert != NULL; + preq->current_cert = sss_cai_get_next(preq->current_cert)) { + + cert_user_objs = sss_cai_get_cert_user_objs(preq->current_cert); + if (cert_user_objs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Unexpected missing certificate user, " + "trying next certificate.\n"); + continue; + } + + for (c = 0; c < cert_user_objs->count; c++) { + cert_user = ldb_msg_find_attr_as_string(cert_user_objs->msgs[c], + SYSDB_NAME, NULL); + if (cert_user == NULL) { + /* Even if there might be other users mapped to the + * certificate a missing SYSDB_NAME indicates some critical + * condition which justifies that the whole request is aborted + * */ + DEBUG(SSSDBG_CRIT_FAILURE, + "Certificate user object has no name.\n"); + preq->pd->pam_status = PAM_USER_UNKNOWN; + pam_reply(preq); + return; + } + + if (ldb_dn_compare(cert_user_objs->msgs[c]->dn, + preq->user_obj->dn) == 0) { + found = true; + if (preq->pd->cmd == SSS_PAM_PREAUTH) { + ret = sss_authtok_set_sc(preq->pd->authtok, + SSS_AUTHTOK_TYPE_SC_PIN, NULL, 0, + sss_cai_get_token_name(preq->current_cert), 0, + sss_cai_get_module_name(preq->current_cert), 0, + sss_cai_get_key_id(preq->current_cert), 0, + sss_cai_get_label(preq->current_cert), 0); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_authtok_set_sc failed, Smartcard " + "authentication detection might fail in " + "the backend.\n"); + } + + ret = add_pam_cert_response(preq->pd, + preq->cctx->rctx->domains, + cert_user, + preq->current_cert, + SSS_PAM_CERT_INFO); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "add_pam_cert_response failed.\n"); + preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + } + } + + } + } + } + + if (found) { + if (local_policy != NULL && strcasecmp(local_policy, "only") == 0) { + talloc_free(tmp_ctx); + DEBUG(SSSDBG_IMPORTANT_INFO, "Local auth only set, skipping online auth\n"); + if (preq->pd->cmd == SSS_PAM_PREAUTH) { + preq->pd->pam_status = PAM_SUCCESS; + } else if (preq->pd->cmd == SSS_PAM_AUTHENTICATE + && IS_SC_AUTHTOK(preq->pd->authtok) + && preq->cert_auth_local) { + preq->pd->pam_status = PAM_SUCCESS; + preq->callback = pam_reply; + } + + pam_reply(preq); + return; + } + + /* We are done if we do not have to call the backend */ + if (preq->pd->cmd == SSS_PAM_AUTHENTICATE + && preq->cert_auth_local) { + preq->pd->pam_status = PAM_SUCCESS; + preq->callback = pam_reply; + pam_reply(preq); + return; + } + } else { + if (preq->pd->cmd == SSS_PAM_PREAUTH) { + DEBUG(SSSDBG_TRACE_FUNC, + "User and certificate user do not match, " + "continue with other authentication methods.\n"); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "User and certificate user do not match.\n"); + preq->pd->pam_status = PAM_AUTH_ERR; + pam_reply(preq); + return; + } + } + } + + if (local_policy != NULL && strcasecmp(local_policy, "only") == 0) { + talloc_free(tmp_ctx); + DEBUG(SSSDBG_IMPORTANT_INFO, "Local auth only set, skipping online auth\n"); + if (preq->pd->cmd == SSS_PAM_PREAUTH) { + preq->pd->pam_status = PAM_SUCCESS; + } else if (preq->pd->cmd == SSS_PAM_AUTHENTICATE && IS_SC_AUTHTOK(preq->pd->authtok)) { + /* Trigger offline smartcardcard autheitcation */ + preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + } + + pam_reply(preq); + return; + } + + preq->callback = pam_reply; + ret = pam_dp_send_req(preq); + DEBUG(SSSDBG_CONF_SETTINGS, "pam_dp_send_req returned %d\n", ret); + + talloc_free(tmp_ctx); + + if (ret != EOK) { + preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(preq); + } +} + +static int pam_cmd_authenticate(struct cli_ctx *cctx) { + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_authenticate\n"); + return pam_forwarder(cctx, SSS_PAM_AUTHENTICATE); +} + +static int pam_cmd_setcred(struct cli_ctx *cctx) { + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_setcred\n"); + return pam_forwarder(cctx, SSS_PAM_SETCRED); +} + +static int pam_cmd_acct_mgmt(struct cli_ctx *cctx) { + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_acct_mgmt\n"); + return pam_forwarder(cctx, SSS_PAM_ACCT_MGMT); +} + +static int pam_cmd_open_session(struct cli_ctx *cctx) { + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_open_session\n"); + return pam_forwarder(cctx, SSS_PAM_OPEN_SESSION); +} + +static int pam_cmd_close_session(struct cli_ctx *cctx) { + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_close_session\n"); + return pam_forwarder(cctx, SSS_PAM_CLOSE_SESSION); +} + +static int pam_cmd_chauthtok(struct cli_ctx *cctx) { + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_chauthtok\n"); + return pam_forwarder(cctx, SSS_PAM_CHAUTHTOK); +} + +static int pam_cmd_chauthtok_prelim(struct cli_ctx *cctx) { + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_chauthtok_prelim\n"); + return pam_forwarder(cctx, SSS_PAM_CHAUTHTOK_PRELIM); +} + +static int pam_cmd_preauth(struct cli_ctx *cctx) +{ + DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_preauth\n"); + return pam_forwarder(cctx, SSS_PAM_PREAUTH); +} + +struct cli_protocol_version *register_cli_protocol_version(void) +{ + static struct cli_protocol_version pam_cli_protocol_version[] = { + {3, "2009-09-14", "make cli_pid mandatory"}, + {2, "2009-05-12", "new format <type><size><data>"}, + {1, "2008-09-05", "initial version, \\0 terminated strings"}, + {0, NULL, NULL} + }; + + return pam_cli_protocol_version; +} + +struct sss_cmd_table *get_pam_cmds(void) +{ + static struct sss_cmd_table sss_cmds[] = { + {SSS_GET_VERSION, sss_cmd_get_version}, + {SSS_PAM_AUTHENTICATE, pam_cmd_authenticate}, + {SSS_PAM_SETCRED, pam_cmd_setcred}, + {SSS_PAM_ACCT_MGMT, pam_cmd_acct_mgmt}, + {SSS_PAM_OPEN_SESSION, pam_cmd_open_session}, + {SSS_PAM_CLOSE_SESSION, pam_cmd_close_session}, + {SSS_PAM_CHAUTHTOK, pam_cmd_chauthtok}, + {SSS_PAM_CHAUTHTOK_PRELIM, pam_cmd_chauthtok_prelim}, + {SSS_PAM_PREAUTH, pam_cmd_preauth}, + {SSS_GSSAPI_INIT, pam_cmd_gssapi_init}, + {SSS_GSSAPI_SEC_CTX, pam_cmd_gssapi_sec_ctx}, + {SSS_CLI_NULL, NULL} + }; + + return sss_cmds; +} + +errno_t +pam_set_last_online_auth_with_curr_token(struct sss_domain_info *domain, + const char *username, + uint64_t value) +{ + TALLOC_CTX *tmp_ctx; + struct sysdb_attrs *attrs; + int ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + attrs = sysdb_new_attrs(tmp_ctx); + if (attrs == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_attrs_add_time_t(attrs, + SYSDB_LAST_ONLINE_AUTH_WITH_CURR_TOKEN, + value); + if (ret != EOK) { goto done; } + + ret = sysdb_set_user_attr(domain, username, attrs, SYSDB_MOD_REP); + if (ret != EOK) { goto done; } + +done: + if (ret != EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "Error: %d (%s)\n", ret, sss_strerror(ret)); + } + + talloc_zfree(tmp_ctx); + return ret; +} + +static errno_t +pam_null_last_online_auth_with_curr_token(struct sss_domain_info *domain, + const char *username) +{ + return pam_set_last_online_auth_with_curr_token(domain, username, 0); +} + +static errno_t +pam_get_last_online_auth_with_curr_token(struct sss_domain_info *domain, + const char *name, + uint64_t *_value) +{ + TALLOC_CTX *tmp_ctx = NULL; + const char *attrs[] = { SYSDB_LAST_ONLINE_AUTH_WITH_CURR_TOKEN, NULL }; + struct ldb_message *ldb_msg; + uint64_t value = 0; + errno_t ret; + + if (name == NULL || *name == '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing user name.\n"); + ret = EINVAL; + goto done; + } + + if (domain->sysdb == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing sysdb db context.\n"); + ret = EINVAL; + goto done; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_user_by_name(tmp_ctx, domain, name, attrs, &ldb_msg); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sysdb_search_user_by_name failed [%d][%s].\n", + ret, strerror(ret)); + goto done; + } + + /* Check offline_auth_cache_timeout */ + value = ldb_msg_find_attr_as_uint64(ldb_msg, + SYSDB_LAST_ONLINE_AUTH_WITH_CURR_TOKEN, + 0); + ret = EOK; + +done: + if (ret == EOK) { + *_value = value; + } + + talloc_free(tmp_ctx); + return ret; +} diff --git a/src/responder/pam/pamsrv_dp.c b/src/responder/pam/pamsrv_dp.c new file mode 100644 index 0000000..881352e --- /dev/null +++ b/src/responder/pam/pamsrv_dp.c @@ -0,0 +1,106 @@ +/* + SSSD + + NSS Responder - Data Provider Interfaces + + Copyright (C) Simo Sorce <ssorce@redhat.com> 2008 + + 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 <talloc.h> +#include <tevent.h> +#include <security/pam_modules.h> + +#include "util/util.h" +#include "util/sss_pam_data.h" +#include "responder/pam/pamsrv.h" +#include "sss_iface/sss_iface_async.h" + +static void +pam_dp_send_req_done(struct tevent_req *subreq); + +errno_t +pam_dp_send_req(struct pam_auth_req *preq) +{ + struct tevent_req *subreq; + struct be_conn *be_conn; + errno_t ret; + + ret = sss_dp_get_domain_conn(preq->cctx->rctx, preq->domain->conn_name, + &be_conn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "The Data Provider connection for %s is not " + "available! This maybe a bug, it shouldn't happen!\n", + preq->domain->conn_name); + return EIO; + } + + DEBUG(SSSDBG_CONF_SETTINGS, "Sending request with the following data:\n"); + DEBUG_PAM_DATA(SSSDBG_CONF_SETTINGS, preq->pd); + + subreq = sbus_call_dp_dp_pamHandler_send(preq, be_conn->conn, + be_conn->bus_name, SSS_BUS_PATH, preq->pd); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + return ENOMEM; + } + + tevent_req_set_callback(subreq, pam_dp_send_req_done, preq); + + return EOK; +} + +static void +pam_dp_send_req_done(struct tevent_req *subreq) +{ + struct pam_data *pam_response; + struct response_data *resp; + struct pam_auth_req *preq; + errno_t ret; + + preq = tevent_req_callback_data(subreq, struct pam_auth_req); + + ret = sbus_call_dp_dp_pamHandler_recv(preq, subreq, &pam_response); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "PAM handler failed [%d]: %s\n", + ret, sss_strerror(ret)); + preq->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + + preq->pd->pam_status = pam_response->pam_status; + preq->pd->account_locked = pam_response->account_locked; + + DEBUG(SSSDBG_FUNC_DATA, "received: [%d (%s)][%s]\n", + pam_response->pam_status, + pam_strerror(NULL, pam_response->pam_status), + preq->pd->domain); + + for (resp = pam_response->resp_list; resp != NULL; resp = resp->next) { + talloc_steal(preq->pd, resp); + + if (resp->next == NULL) { + resp->next = preq->pd->resp_list; + preq->pd->resp_list = pam_response->resp_list; + break; + } + } + + talloc_zfree(pam_response); + +done: + preq->callback(preq); +} diff --git a/src/responder/pam/pamsrv_gssapi.c b/src/responder/pam/pamsrv_gssapi.c new file mode 100644 index 0000000..e4da4c4 --- /dev/null +++ b/src/responder/pam/pamsrv_gssapi.c @@ -0,0 +1,1043 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2020 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 <gssapi.h> +#include <gssapi/gssapi_ext.h> +#include <gssapi/gssapi_krb5.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <talloc.h> +#include <ldb.h> + +#include "confdb/confdb.h" +#include "db/sysdb.h" +#include "responder/common/responder_packet.h" +#include "responder/common/responder.h" +#include "responder/common/cache_req/cache_req.h" +#include "responder/pam/pamsrv.h" +#include "sss_client/sss_cli.h" +#include "util/util.h" +#include "util/sss_utf8.h" + +static errno_t read_str(size_t body_len, + uint8_t *body, + size_t *pctr, + const char **_str) +{ + size_t i; + + for (i = *pctr; i < body_len && body[i] != 0; i++) { + /* counting */ + } + + if (i >= body_len) { + return EINVAL; + } + + if (!sss_utf8_check(&body[*pctr], i - *pctr)) { + DEBUG(SSSDBG_CRIT_FAILURE, "Body is not UTF-8 string!\n"); + return EINVAL; + } + + *_str = (const char *)&body[*pctr]; + *pctr = i + 1; + + return EOK; +} + +static bool pam_gssapi_should_check_upn(struct pam_ctx *pam_ctx, + struct sss_domain_info *domain) +{ + if (domain->gssapi_check_upn != NULL) { + if (strcasecmp(domain->gssapi_check_upn, "true") == 0) { + return true; + } + + if (strcasecmp(domain->gssapi_check_upn, "false") == 0) { + return false; + } + + DEBUG(SSSDBG_MINOR_FAILURE, "Invalid value for %s: %s\n", + CONFDB_PAM_GSSAPI_CHECK_UPN, domain->gssapi_check_upn); + return false; + } + + return pam_ctx->gssapi_check_upn; +} + +static int pam_gssapi_check_indicators(TALLOC_CTX *mem_ctx, + const char *pam_service, + char **gssapi_indicators_map, + char **indicators) +{ + char *authind = NULL; + size_t pam_len = strlen(pam_service); + char **map = gssapi_indicators_map; + char **result = NULL; + int res; + + authind = talloc_strdup(mem_ctx, ""); + if (authind == NULL) { + return ENOMEM; + } + + for (int i = 0; map[i]; i++) { + if (map[i][0] == '-') { + DEBUG(SSSDBG_TRACE_FUNC, + "Indicators aren't used for [%s]\n", + pam_service); + talloc_free(authind); + return EOK; + } + if (!strchr(map[i], ':')) { + authind = talloc_asprintf_append(authind, "%s ", map[i]); + if (authind == NULL) { + /* Since we allocate on pam_ctx, caller will free it */ + return ENOMEM; + } + continue; + } + + res = strncmp(map[i], pam_service, pam_len); + if (res == 0) { + if (strlen(map[i]) > pam_len) { + if (map[i][pam_len] != ':') { + /* different PAM service, skip it */ + continue; + } + + if (map[i][pam_len + 1] == '-') { + DEBUG(SSSDBG_TRACE_FUNC, + "Indicators aren't used for [%s]\n", + pam_service); + talloc_free(authind); + return EOK; + } + + authind = talloc_asprintf_append(authind, "%s ", + map[i] + (pam_len + 1)); + if (authind == NULL) { + /* Since we allocate on pam_ctx, caller will free it */ + return ENOMEM; + } + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "Invalid value for %s: [%s]\n", + CONFDB_PAM_GSSAPI_INDICATORS_MAP, map[i]); + talloc_free(authind); + return EINVAL; + } + } + } + + res = ENOENT; + map = NULL; + + if (authind[0] == '\0') { + /* empty list of per-service indicators -> skip */ + goto done; + } + + /* trim a space after the final indicator + * to prevent split_on_separator() to fail */ + authind[strlen(authind) - 1] = '\0'; + + res = split_on_separator(mem_ctx, authind, ' ', true, true, + &map, NULL); + if (res != 0) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Cannot parse list of indicators: [%s]\n", authind); + res = EINVAL; + goto done; + } + + res = diff_string_lists(mem_ctx, indicators, map, NULL, NULL, &result); + if (res != 0) { + DEBUG(SSSDBG_FATAL_FAILURE,"Cannot diff lists of indicators\n"); + res = EINVAL; + goto done; + } + + if (result && result[0] != NULL) { + for (int i = 0; result[i]; i++) { + DEBUG(SSSDBG_TRACE_FUNC, + "indicator [%s] is allowed for PAM service [%s]\n", + result[i], pam_service); + } + res = EOK; + goto done; + } + + res = EPERM; + +done: + talloc_free(result); + talloc_free(authind); + talloc_free(map); + return res; +} + +static bool pam_gssapi_allowed(struct pam_ctx *pam_ctx, + struct sss_domain_info *domain, + const char *service) +{ + char **list = pam_ctx->gssapi_services; + + if (domain->gssapi_services != NULL) { + list = domain->gssapi_services; + } + + if (strcmp(service, "-") == 0) { + /* Dash is used as a "not set" value to allow to explicitly disable + * gssapi auth for specific domain. Disallow this service to be safe. + */ + DEBUG(SSSDBG_TRACE_FUNC, "Dash - was used as a PAM service name. " + "GSSAPI authentication is not allowed.\n"); + return false; + } + + return string_in_list(service, list, true); +} + +static char *pam_gssapi_target(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain) +{ + return talloc_asprintf(mem_ctx, "host@%s", domain->hostname); +} + +static const char *pam_gssapi_get_upn(struct cache_req_result *result) +{ + if (result->count == 0) { + return NULL; + } + + /* Canonical UPN should be available if the user has kinited through SSSD. + * Use it as a hint for GSSAPI. Default to empty string so it may be + * more easily transffered over the wire. */ + return ldb_msg_find_attr_as_string(result->msgs[0], SYSDB_CANONICAL_UPN, ""); +} + +static const char *pam_gssapi_get_name(struct cache_req_result *result) +{ + if (result->count == 0) { + return NULL; + } + + /* Return username known to SSSD to make sure we authenticated as the same + * user after GSSAPI handshake. */ + return ldb_msg_find_attr_as_string(result->msgs[0], SYSDB_NAME, NULL); +} + +static errno_t pam_gssapi_init_parse(struct cli_protocol *pctx, + const char **_service, + const char **_username) +{ + size_t body_len; + size_t pctr = 0; + uint8_t *body; + errno_t ret; + + sss_packet_get_body(pctx->creq->in, &body, &body_len); + if (body == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid input\n"); + return EINVAL; + } + + ret = read_str(body_len, body, &pctr, _service); + if (ret != EOK) { + return ret; + } + + ret = read_str(body_len, body, &pctr, _username); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +static errno_t pam_gssapi_init_reply(struct cli_protocol *pctx, + const char *domain, + const char *target, + const char *upn, + const char *username) +{ + size_t reply_len; + size_t body_len; + size_t pctr; + uint8_t *body; + errno_t ret; + + ret = sss_packet_new(pctx->creq, 0, sss_packet_get_cmd(pctx->creq->in), + &pctx->creq->out); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create a new packet [%d]; %s\n", + ret, sss_strerror(ret)); + return ret; + } + + reply_len = strlen(username) + 1; + reply_len += strlen(domain) + 1; + reply_len += strlen(target) + 1; + reply_len += strlen(upn) + 1; + + ret = sss_packet_grow(pctx->creq->out, reply_len); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create response: %s\n", + sss_strerror(ret)); + return ret; + } + + sss_packet_get_body(pctx->creq->out, &body, &body_len); + + pctr = 0; + SAFEALIGN_SETMEM_STRING(&body[pctr], username, strlen(username) + 1, &pctr); + SAFEALIGN_SETMEM_STRING(&body[pctr], domain, strlen(domain) + 1, &pctr); + SAFEALIGN_SETMEM_STRING(&body[pctr], target, strlen(target) + 1, &pctr); + SAFEALIGN_SETMEM_STRING(&body[pctr], upn, strlen(upn) + 1, &pctr); + + return EOK; +} + +struct gssapi_init_state { + struct cli_ctx *cli_ctx; + const char *username; + const char *service; +}; + +static void pam_cmd_gssapi_init_done(struct tevent_req *req); + +int pam_cmd_gssapi_init(struct cli_ctx *cli_ctx) +{ + struct gssapi_init_state *state; + struct cli_protocol *pctx; + struct tevent_req *req; + const char *username; + const char *service; + const char *attrs[] = { SYSDB_NAME, SYSDB_CANONICAL_UPN, NULL }; + errno_t ret; + + state = talloc_zero(cli_ctx, struct gssapi_init_state); + if (state == NULL) { + return ENOMEM; + } + + pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol); + + ret = pam_gssapi_init_parse(pctx, &service, &username); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse input [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + state->cli_ctx = cli_ctx; + state->service = service; + state->username = username; + + DEBUG(SSSDBG_TRACE_ALL, + "Requesting GSSAPI authentication of [%s] in service [%s]\n", + username, service); + + req = cache_req_user_by_name_attrs_send(cli_ctx, cli_ctx->ev, cli_ctx->rctx, + cli_ctx->rctx->ncache, 0, + NULL, username, attrs); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(req, pam_cmd_gssapi_init_done, state); + + ret = EOK; + +done: + if (ret != EOK) { + sss_cmd_send_error(cli_ctx, ret); + sss_cmd_done(cli_ctx, NULL); + } + + return EOK; +} + +static void pam_cmd_gssapi_init_done(struct tevent_req *req) +{ + struct gssapi_init_state *state; + struct cache_req_result *result; + struct cli_protocol *pctx; + struct pam_ctx *pam_ctx; + const char *username; + const char *upn; + char *target; + errno_t ret; + + state = tevent_req_callback_data(req, struct gssapi_init_state); + pctx = talloc_get_type(state->cli_ctx->protocol_ctx, struct cli_protocol); + pam_ctx = talloc_get_type(state->cli_ctx->rctx->pvt_ctx, struct pam_ctx); + + ret = cache_req_user_by_name_attrs_recv(state, req, &result); + talloc_zfree(req); + if (ret == ENOENT || ret == ERR_DOMAIN_NOT_FOUND) { + ret = ENOENT; + goto done; + } else if (ret != EOK) { + goto done; + } + + if (!pam_gssapi_allowed(pam_ctx, result->domain, state->service)) { + ret = ENOTSUP; + goto done; + } + + username = pam_gssapi_get_name(result); + if (username == NULL) { + /* User with no name? */ + ret = ERR_INTERNAL; + goto done; + } + + upn = pam_gssapi_get_upn(result); + if (upn == NULL) { + /* UPN hint may be an empty string, but not NULL. */ + ret = ERR_INTERNAL; + goto done; + } + + target = pam_gssapi_target(state, result->domain); + if (target == NULL) { + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Trying GSSAPI auth: User[%s], Domain[%s], UPN[%s], Target[%s]\n", + username, result->domain->name, upn, target); + + ret = pam_gssapi_init_reply(pctx, result->domain->name, target, upn, + username); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to construct reply [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + +done: + DEBUG(SSSDBG_TRACE_FUNC, "Returning [%d]: %s\n", ret, sss_strerror(ret)); + + if (ret == EOK) { + sss_packet_set_error(pctx->creq->out, EOK); + } else { + sss_cmd_send_error(state->cli_ctx, ret); + } + + sss_cmd_done(state->cli_ctx, state); +} + +static void gssapi_log_status(int type, OM_uint32 status_code) +{ + OM_uint32 message_context = 0; + gss_buffer_desc buf; + OM_uint32 minor; + + do { + gss_display_status(&minor, status_code, type, GSS_C_NO_OID, + &message_context, &buf); + DEBUG(SSSDBG_OP_FAILURE, "GSSAPI: %.*s\n", (int)buf.length, + (char *)buf.value); + gss_release_buffer(&minor, &buf); + } while (message_context != 0); +} + +static void gssapi_log_error(OM_uint32 major, OM_uint32 minor) +{ + gssapi_log_status(GSS_C_GSS_CODE, major); + gssapi_log_status(GSS_C_MECH_CODE, minor); +} + +static char *gssapi_get_name(TALLOC_CTX *mem_ctx, gss_name_t gss_name) +{ + gss_buffer_desc buf; + OM_uint32 major; + OM_uint32 minor; + char *exported; + + major = gss_display_name(&minor, gss_name, &buf, NULL); + if (major != GSS_S_COMPLETE) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to export name\n"); + return NULL; + } + + exported = talloc_strndup(mem_ctx, buf.value, buf.length); + gss_release_buffer(&minor, &buf); + + if (exported == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory!\n"); + return NULL; + } + + return exported; +} + +#define AUTH_INDICATORS_TAG "auth-indicators" + +static char **gssapi_get_indicators(TALLOC_CTX *mem_ctx, gss_name_t gss_name) +{ + gss_buffer_set_t attrs = GSS_C_NO_BUFFER_SET; + int is_mechname; + OM_uint32 major; + OM_uint32 minor; + gss_buffer_desc value = GSS_C_EMPTY_BUFFER; + gss_buffer_desc display_value = GSS_C_EMPTY_BUFFER; + char *exported = NULL; + char **map = NULL; + int res; + + major = gss_inquire_name(&minor, gss_name, &is_mechname, NULL, &attrs); + if (major != GSS_S_COMPLETE) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to inquire name\n"); + return NULL; + } + + if (attrs == GSS_C_NO_BUFFER_SET) { + DEBUG(SSSDBG_TRACE_FUNC, "No krb5 attributes in the ticket\n"); + return NULL; + } + + exported = talloc_strdup(mem_ctx, ""); + if (exported == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to pre-allocate indicators\n"); + goto done; + } + + for (int i = 0; i < attrs->count; i++) { + int authenticated = 0; + int complete = 0; + int more = -1; + + /* skip anything but auth-indicators */ + if (strncmp(AUTH_INDICATORS_TAG, attrs->elements[i].value, + sizeof(AUTH_INDICATORS_TAG) - 1) != 0) + continue; + + /* retrieve all indicators */ + while (more != 0) { + value.value = NULL; + display_value.value = NULL; + + major = gss_get_name_attribute(&minor, gss_name, + &attrs->elements[i], + &authenticated, + &complete, &value, + &display_value, + &more); + if (major != GSS_S_COMPLETE) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to retrieve an attribute\n"); + goto done; + } + + if ((value.value != NULL) && authenticated) { + DEBUG(SSSDBG_TRACE_FUNC, + "attribute's [%.*s] value [%.*s] authenticated\n", + (int) attrs->elements[i].length, + (char*) attrs->elements[i].value, + (int) value.length, + (char*) value.value); + exported = talloc_asprintf_append(exported, "%.*s ", + (int) value.length, + (char*) value.value); + } + + if (exported == NULL) { + /* Since we allocate on mem_ctx, caller will free + * the previous version of 'exported' */ + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to collect an attribute value\n"); + goto done; + } + (void) gss_release_buffer(&minor, &value); + (void) gss_release_buffer(&minor, &display_value); + } + } + + if (exported[0] != '\0') { + /* trim a space after the final indicator + * to prevent split_on_separator() to fail */ + exported[strlen(exported) - 1] = '\0'; + } else { + /* empty list */ + goto done; + } + + res = split_on_separator(mem_ctx, exported, ' ', true, true, + &map, NULL); + if (res != 0) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Cannot parse list of indicators: [%s]\n", exported); + goto done; + } else { + DEBUG(SSSDBG_TRACE_FUNC, "authentication indicators: [%s]\n", + exported); + } + +done: + (void) gss_release_buffer(&minor, &value); + (void) gss_release_buffer(&minor, &display_value); + (void) gss_release_buffer_set(&minor, &attrs); + + talloc_free(exported); + return map; +} + + +struct gssapi_state { + struct cli_ctx *cli_ctx; + struct sss_domain_info *domain; + const char *username; + + char *authenticated_upn; + char **auth_indicators; + bool established; + gss_ctx_id_t ctx; +}; + +int gssapi_state_destructor(struct gssapi_state *state) +{ + OM_uint32 minor; + + gss_delete_sec_context(&minor, &state->ctx, NULL); + + return 0; +} + +static struct gssapi_state *gssapi_get_state(struct cli_ctx *cli_ctx, + const char *username, + struct sss_domain_info *domain) +{ + struct gssapi_state *state; + + state = talloc_get_type(cli_ctx->state_ctx, struct gssapi_state); + if (state != NULL) { + return state; + } + + state = talloc_zero(cli_ctx, struct gssapi_state); + if (state == NULL) { + return NULL; + } + + state->username = talloc_strdup(state, username); + if (state == NULL) { + talloc_free(state); + return NULL; + } + + state->domain = domain; + state->cli_ctx = cli_ctx; + state->ctx = GSS_C_NO_CONTEXT; + talloc_set_destructor(state, gssapi_state_destructor); + + cli_ctx->state_ctx = state; + + return state; +} + +static errno_t gssapi_get_creds(const char *keytab, + const char *target, + gss_cred_id_t *_creds) +{ + gss_key_value_set_desc cstore = {0, NULL}; + gss_key_value_element_desc el; + gss_buffer_desc name_buf; + gss_name_t name = GSS_C_NO_NAME; + OM_uint32 major; + OM_uint32 minor; + errno_t ret; + + if (keytab != NULL) { + el.key = "keytab"; + el.value = keytab; + cstore.count = 1; + cstore.elements = ⪙ + } + + if (target != NULL) { + name_buf.value = discard_const(target); + name_buf.length = strlen(target); + + major = gss_import_name(&minor, &name_buf, GSS_C_NT_HOSTBASED_SERVICE, + &name); + if (GSS_ERROR(major)) { + DEBUG(SSSDBG_OP_FAILURE, "Could not import name [%s] " + "[maj:0x%x, min:0x%x]\n", target, major, minor); + + gssapi_log_error(major, minor); + + ret = EIO; + goto done; + } + } + + major = gss_acquire_cred_from(&minor, name, GSS_C_INDEFINITE, + GSS_C_NO_OID_SET, GSS_C_ACCEPT, &cstore, + _creds, NULL, NULL); + if (GSS_ERROR(major)) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to read credentials from [%s] " + "[maj:0x%x, min:0x%x]\n", keytab ? keytab : "default", + major, minor); + + gssapi_log_error(major, minor); + + ret = EIO; + goto done; + } + + ret = EOK; + +done: + gss_release_name(&minor, &name); + + return ret; +} + +static errno_t +gssapi_handshake(struct gssapi_state *state, + struct cli_protocol *pctx, + const char *keytab, + const char *target, + uint8_t *gss_data, + size_t gss_data_len) +{ + OM_uint32 flags = GSS_C_MUTUAL_FLAG; + gss_buffer_desc output = GSS_C_EMPTY_BUFFER; + gss_buffer_desc input; + gss_name_t client_name; + gss_cred_id_t creds; + OM_uint32 ret_flags; + gss_OID mech_type; + OM_uint32 major; + OM_uint32 minor; + errno_t ret; + + input.value = gss_data; + input.length = gss_data_len; + + ret = gssapi_get_creds(keytab, target, &creds); + if (ret != EOK) { + return ret; + } + + major = gss_accept_sec_context(&minor, &state->ctx, creds, + &input, NULL, &client_name, &mech_type, + &output, &ret_flags, NULL, NULL); + if (major == GSS_S_CONTINUE_NEEDED || output.length > 0) { + ret = sss_packet_set_body(pctx->creq->out, output.value, output.length); + if (ret != EOK) { + goto done; + } + } + + if (GSS_ERROR(major)) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to establish GSS context " + "[maj:0x%x, min:0x%x]\n", major, minor); + + gssapi_log_error(major, minor); + ret = EIO; + goto done; + } + + if (major == GSS_S_CONTINUE_NEEDED) { + ret = EOK; + goto done; + } else if (major != GSS_S_COMPLETE) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to establish GSS context, unexpected " + "value: 0x%x\n", major); + ret = EIO; + goto done; + } + + if ((ret_flags & flags) != flags) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Negotiated context does not support requested flags\n"); + state->established = false; + ret = EIO; + goto done; + } + + state->authenticated_upn = gssapi_get_name(state, client_name); + if (state->authenticated_upn == NULL) { + state->established = false; + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Security context established with [%s]\n", + state->authenticated_upn); + + state->auth_indicators = gssapi_get_indicators(state, client_name); + + state->established = true; + ret = EOK; + +done: + gss_release_cred(&minor, &creds); + gss_release_buffer(&minor, &output); + + return ret; +} + +static errno_t pam_cmd_gssapi_sec_ctx_parse(struct cli_protocol *pctx, + const char **_pam_service, + const char **_username, + const char **_domain, + uint8_t **_gss_data, + size_t *_gss_data_len) +{ + size_t body_len; + uint8_t *body; + size_t pctr; + errno_t ret; + + sss_packet_get_body(pctx->creq->in, &body, &body_len); + if (body == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid input\n"); + return EINVAL; + } + + pctr = 0; + ret = read_str(body_len, body, &pctr, _pam_service); + if (ret != EOK) { + return ret; + } + + ret = read_str(body_len, body, &pctr, _username); + if (ret != EOK) { + return ret; + } + + ret = read_str(body_len, body, &pctr, _domain); + if (ret != EOK) { + return ret; + } + + *_gss_data = (pctr == body_len) ? NULL : body + pctr; + *_gss_data_len = body_len - pctr; + + return EOK; +} + +static void pam_cmd_gssapi_sec_ctx_done(struct tevent_req *req); + +int +pam_cmd_gssapi_sec_ctx(struct cli_ctx *cli_ctx) +{ + struct sss_domain_info *domain; + struct gssapi_state *state; + struct cli_protocol *pctx; + struct pam_ctx *pam_ctx; + struct tevent_req *req; + const char *pam_service; + const char *domain_name; + const char *username; + char *target; + char **indicators_map = NULL; + size_t gss_data_len; + uint8_t *gss_data; + errno_t ret; + + pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol); + pam_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct pam_ctx); + + ret = sss_packet_new(pctx->creq, 0, sss_packet_get_cmd(pctx->creq->in), + &pctx->creq->out); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create a new packet [%d]; %s\n", + ret, sss_strerror(ret)); + return ret; + } + + ret = pam_cmd_gssapi_sec_ctx_parse(pctx, &pam_service, &username, + &domain_name, &gss_data, &gss_data_len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to parse input data [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + domain = find_domain_by_name(cli_ctx->rctx->domains, domain_name, false); + if (domain == NULL) { + ret = EINVAL; + goto done; + } + + if (!pam_gssapi_allowed(pam_ctx, domain, pam_service)) { + ret = ENOTSUP; + goto done; + } + + target = pam_gssapi_target(cli_ctx, domain); + if (target == NULL) { + ret = ENOMEM; + goto done; + } + + state = gssapi_get_state(cli_ctx, username, domain); + if (state == NULL) { + ret = ENOMEM; + goto done; + } + + if (strcmp(username, state->username) != 0 || state->domain != domain) { + /* This should not happen, but be paranoid. */ + DEBUG(SSSDBG_CRIT_FAILURE, "Different input user then who initiated " + "the request!\n"); + ret = EPERM; + goto done; + } + + if (state->established) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Security context is already established\n"); + ret = EPERM; + goto done; + } + + ret = gssapi_handshake(state, pctx, domain->krb5_keytab, target, gss_data, + gss_data_len); + if (ret != EOK || !state->established) { + goto done; + } + + /* Use map for auth-indicators from the domain, if defined and + * fallback to the [pam] section otherwise */ + indicators_map = domain->gssapi_indicators_map ? + domain->gssapi_indicators_map : + (pam_ctx->gssapi_indicators_map ? + pam_ctx->gssapi_indicators_map : NULL); + if (indicators_map != NULL) { + ret = pam_gssapi_check_indicators(state, + pam_service, + indicators_map, + state->auth_indicators); + DEBUG(SSSDBG_TRACE_FUNC, + "Check if acquired service ticket has req. indicators: %d\n", + ret); + if ((ret == EPERM) || (ret == ENOMEM) || (ret == EINVAL)) { + /* skip further checks if denied or no memory, + * ENOENT means the check is not applicable */ + goto done; + } + } + + if (!pam_gssapi_should_check_upn(pam_ctx, domain)) { + /* We are done. */ + goto done; + } + + /* We have established the security context. Now check the the principal + * used for authorization can be associated with the user. We have + * already done initgroups before so we could just search the sysdb + * directly, but use cache req to avoid looking up a possible expired + * object if the handshake took longer. */ + + DEBUG(SSSDBG_TRACE_FUNC, "Checking that target user matches UPN\n"); + + req = cache_req_user_by_upn_send(cli_ctx, cli_ctx->ev, cli_ctx->rctx, + cli_ctx->rctx->ncache, 0, + CACHE_REQ_POSIX_DOM, + domain->name, state->authenticated_upn); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(req, pam_cmd_gssapi_sec_ctx_done, state); + + return EOK; + +done: + DEBUG(SSSDBG_TRACE_FUNC, "Returning [%d]: %s\n", ret, sss_strerror(ret)); + + if (ret == EOK) { + sss_packet_set_error(pctx->creq->out, EOK); + } else { + sss_cmd_send_error(cli_ctx, ret); + } + + sss_cmd_done(cli_ctx, NULL); + return EOK; +} + +static void pam_cmd_gssapi_sec_ctx_done(struct tevent_req *req) +{ + struct gssapi_state *state; + struct cache_req_result *result; + struct cli_protocol *pctx; + const char *name; + errno_t ret; + + state = tevent_req_callback_data(req, struct gssapi_state); + pctx = talloc_get_type(state->cli_ctx->protocol_ctx, struct cli_protocol); + + ret = cache_req_user_by_upn_recv(state, req, &result); + talloc_zfree(req); + if (ret == ENOENT || ret == ERR_DOMAIN_NOT_FOUND) { + /* We have no match. Return failure. */ + DEBUG(SSSDBG_TRACE_FUNC, "User with UPN [%s] was not found. " + "Authentication failed.\n", state->authenticated_upn); + ret = EACCES; + goto done; + } else if (ret != EOK) { + /* Generic error. Return failure. */ + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to lookup user by UPN [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* Check that username match. */ + name = ldb_msg_find_attr_as_string(result->msgs[0], SYSDB_NAME, NULL); + if (name == NULL || strcmp(name, state->username) != 0) { + DEBUG(SSSDBG_TRACE_FUNC, "UPN [%s] does not match target user [%s]. " + "Authentication failed.\n", state->authenticated_upn, + state->username); + ret = EACCES; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "User [%s] match UPN [%s]. Authentication was " + "successful.\n", state->username, state->authenticated_upn); + + ret = EOK; + +done: + DEBUG(SSSDBG_TRACE_FUNC, "Returning [%d]: %s\n", ret, sss_strerror(ret)); + + if (ret == EOK) { + sss_packet_set_error(pctx->creq->out, EOK); + } else { + sss_cmd_send_error(state->cli_ctx, ret); + } + + sss_cmd_done(state->cli_ctx, state); +} diff --git a/src/responder/pam/pamsrv_p11.c b/src/responder/pam/pamsrv_p11.c new file mode 100644 index 0000000..2f973d8 --- /dev/null +++ b/src/responder/pam/pamsrv_p11.c @@ -0,0 +1,1312 @@ +/* + SSSD + + PAM Responder - certificate related requests + + Copyright (C) Sumit Bose <sbose@redhat.com> 2015 + + 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 <time.h> + +#include "util/util.h" +#include "providers/data_provider.h" +#include "util/child_common.h" +#include "util/strtonum.h" +#include "responder/pam/pamsrv.h" +#include "responder/pam/pam_helpers.h" +#include "lib/certmap/sss_certmap.h" +#include "util/crypto/sss_crypto.h" +#include "util/sss_chain_id.h" +#include "db/sysdb.h" + + +struct cert_auth_info { + char *cert; + char *token_name; + char *module_name; + char *key_id; + char *label; + struct ldb_result *cert_user_objs; + struct cert_auth_info *prev; + struct cert_auth_info *next; +}; + +const char *sss_cai_get_cert(struct cert_auth_info *i) +{ + return i != NULL ? i->cert : NULL; +} + +const char *sss_cai_get_token_name(struct cert_auth_info *i) +{ + return i != NULL ? i->token_name : NULL; +} + +const char *sss_cai_get_module_name(struct cert_auth_info *i) +{ + return i != NULL ? i->module_name : NULL; +} + +const char *sss_cai_get_key_id(struct cert_auth_info *i) +{ + return i != NULL ? i->key_id : NULL; +} + +const char *sss_cai_get_label(struct cert_auth_info *i) +{ + return i != NULL ? i->label : NULL; +} + +struct cert_auth_info *sss_cai_get_next(struct cert_auth_info *i) +{ + return i != NULL ? i->next : NULL; +} + +struct ldb_result *sss_cai_get_cert_user_objs(struct cert_auth_info *i) +{ + return i != NULL ? i->cert_user_objs : NULL; +} + +void sss_cai_set_cert_user_objs(struct cert_auth_info *i, + struct ldb_result *cert_user_objs) +{ + if (i->cert_user_objs != NULL) { + talloc_free(i->cert_user_objs); + } + i->cert_user_objs = talloc_steal(i, cert_user_objs); +} + +void sss_cai_check_users(struct cert_auth_info **list, size_t *_cert_count, + size_t *_cert_user_count) +{ + struct cert_auth_info *c; + struct cert_auth_info *tmp; + size_t cert_count = 0; + size_t cert_user_count = 0; + struct ldb_result *user_objs; + + DLIST_FOR_EACH_SAFE(c, tmp, *list) { + user_objs = sss_cai_get_cert_user_objs(c); + if (user_objs != NULL) { + cert_count++; + cert_user_count += user_objs->count; + } else { + DLIST_REMOVE(*list, c); + } + } + + if (_cert_count != NULL) { + *_cert_count = cert_count; + } + + if (_cert_user_count != NULL) { + *_cert_user_count = cert_user_count; + } + + return; +} + +struct priv_sss_debug { + int level; +}; + +static void ext_debug(void *private, const char *file, long line, + const char *function, const char *format, ...) +{ + va_list ap; + struct priv_sss_debug *data = private; + int level = SSSDBG_OP_FAILURE; + + if (data != NULL) { + level = data->level; + } + + va_start(ap, format); + sss_vdebug_fn(file, line, function, level, APPEND_LINE_FEED, + format, ap); + va_end(ap); +} + +errno_t p11_refresh_certmap_ctx(struct pam_ctx *pctx, + struct sss_domain_info *domains) +{ + int ret; + struct sss_certmap_ctx *sss_certmap_ctx = NULL; + size_t c; + struct sss_domain_info *dom; + bool certmap_found = false; + struct certmap_info **certmap_list; + + ret = sss_certmap_init(pctx, ext_debug, NULL, &sss_certmap_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_certmap_init failed.\n"); + goto done; + } + + DLIST_FOR_EACH(dom, domains) { + certmap_list = dom->certmaps; + if (certmap_list != NULL && *certmap_list != NULL) { + certmap_found = true; + break; + } + } + + if (!certmap_found) { + /* Try to add default matching rule */ + ret = sss_certmap_add_rule(sss_certmap_ctx, SSS_CERTMAP_MIN_PRIO, + CERT_AUTH_DEFAULT_MATCHING_RULE, NULL, NULL); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to add default matching rule.\n"); + } + + goto done; + } + + DLIST_FOR_EACH(dom, domains) { + certmap_list = dom->certmaps; + if (certmap_list == NULL || *certmap_list == NULL) { + continue; + } + + for (c = 0; certmap_list[c] != NULL; c++) { + DEBUG(SSSDBG_TRACE_ALL, + "Trying to add rule [%s][%d][%s][%s].\n", + certmap_list[c]->name, certmap_list[c]->priority, + certmap_list[c]->match_rule, certmap_list[c]->map_rule); + + ret = sss_certmap_add_rule(sss_certmap_ctx, + certmap_list[c]->priority, + certmap_list[c]->match_rule, + certmap_list[c]->map_rule, + certmap_list[c]->domains); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_certmap_add_rule failed for rule [%s] " + "with error [%d][%s], skipping. " + "Please check for typos and if rule syntax is supported.\n", + certmap_list[c]->name, ret, sss_strerror(ret)); + continue; + } + } + } + + ret = EOK; + +done: + if (ret == EOK) { + sss_certmap_free_ctx(pctx->sss_certmap_ctx); + pctx->sss_certmap_ctx = sss_certmap_ctx; + } else { + sss_certmap_free_ctx(sss_certmap_ctx); + } + + return ret; +} + +errno_t p11_child_init(struct pam_ctx *pctx) +{ + int ret; + struct certmap_info **certmaps; + bool user_name_hint; + struct sss_domain_info *dom; + + DLIST_FOR_EACH(dom, pctx->rctx->domains) { + ret = sysdb_get_certmap(dom, dom->sysdb, &certmaps, &user_name_hint); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sysdb_get_certmap failed.\n"); + return ret; + } + + dom->user_name_hint = user_name_hint; + talloc_free(dom->certmaps); + dom->certmaps = certmaps; + } + + ret = p11_refresh_certmap_ctx(pctx, pctx->rctx->domains); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "p11_refresh_certmap_ctx failed.\n"); + return ret; + } + + return EOK; +} + +static inline bool +service_in_list(char **list, size_t nlist, const char *str) +{ + size_t i; + + for (i = 0; i < nlist; i++) { + if (strcasecmp(list[i], str) == 0) { + break; + } + } + + return (i < nlist) ? true : false; +} + +static errno_t get_sc_services(TALLOC_CTX *mem_ctx, struct pam_ctx *pctx, + char ***_sc_list) +{ + TALLOC_CTX *tmp_ctx; + errno_t ret; + char *conf_str; + char **conf_list; + int conf_list_size; + char **add_list; + char **remove_list; + int ai = 0; + int ri = 0; + int j = 0; + char **sc_list; + int expected_sc_list_size; + + const char *default_sc_services[] = { + "login", "su", "su-l", "gdm-smartcard", "gdm-password", "kdm", "sudo", + "sudo-i", "gnome-screensaver", "polkit-1", NULL, + }; + const int default_sc_services_size = + sizeof(default_sc_services) / sizeof(default_sc_services[0]); + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = confdb_get_string(pctx->rctx->cdb, tmp_ctx, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_P11_ALLOWED_SERVICES, NULL, + &conf_str); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "confdb_get_string failed %d [%s]\n", ret, sss_strerror(ret)); + goto done; + } + + if (conf_str != NULL) { + ret = split_on_separator(tmp_ctx, conf_str, ',', true, true, + &conf_list, &conf_list_size); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Cannot parse list of service names '%s': %d [%s]\n", + conf_str, ret, sss_strerror(ret)); + goto done; + } + } else { + conf_list = talloc_zero_array(tmp_ctx, char *, 1); + conf_list_size = 0; + } + + add_list = talloc_zero_array(tmp_ctx, char *, conf_list_size + 1); + remove_list = talloc_zero_array(tmp_ctx, char *, conf_list_size + 1); + + if (add_list == NULL || remove_list == NULL) { + ret = ENOMEM; + goto done; + } + + for (int i = 0; conf_list[i] != NULL; ++i) { + switch (conf_list[i][0]) { + case '+': + add_list[ai] = conf_list[i] + 1; + ++ai; + break; + case '-': + remove_list[ri] = conf_list[i] + 1; + ++ri; + break; + default: + DEBUG(SSSDBG_OP_FAILURE, + "The option "CONFDB_PAM_P11_ALLOWED_SERVICES" must start" + "with either '+' (for adding service) or '-' (for " + "removing service) got '%s'\n", conf_list[i]); + ret = EINVAL; + goto done; + } + } + + expected_sc_list_size = default_sc_services_size + ai + 1; + + sc_list = talloc_zero_array(tmp_ctx, char *, expected_sc_list_size); + if (sc_list == NULL) { + ret = ENOMEM; + goto done; + } + + for (int i = 0; add_list[i] != NULL; ++i) { + if (service_in_list(remove_list, ri, add_list[i])) { + continue; + } + + sc_list[j] = talloc_strdup(sc_list, add_list[i]); + if (sc_list[j] == NULL) { + ret = ENOMEM; + goto done; + } + ++j; + } + + for (int i = 0; default_sc_services[i] != NULL; ++i) { + if (service_in_list(remove_list, ri, default_sc_services[i])) { + continue; + } + + sc_list[j] = talloc_strdup(sc_list, default_sc_services[i]); + if (sc_list[j] == NULL) { + ret = ENOMEM; + goto done; + } + ++j; + } + + if (_sc_list != NULL) { + *_sc_list = talloc_steal(mem_ctx, sc_list); + } + +done: + talloc_zfree(tmp_ctx); + + return ret; +} + +bool may_do_cert_auth(struct pam_ctx *pctx, struct pam_data *pd) +{ + size_t c; + errno_t ret; + + if (!pctx->cert_auth) { + return false; + } + + if (pd->cmd != SSS_PAM_PREAUTH && pd->cmd != SSS_PAM_AUTHENTICATE) { + return false; + } + + if (pd->cmd == SSS_PAM_AUTHENTICATE + && sss_authtok_get_type(pd->authtok) != SSS_AUTHTOK_TYPE_SC_PIN + && sss_authtok_get_type(pd->authtok) != SSS_AUTHTOK_TYPE_SC_KEYPAD) { + return false; + } + + if (pd->service == NULL || *pd->service == '\0') { + return false; + } + + /* Initialize smartcard allowed services just once */ + if (pctx->smartcard_services == NULL) { + ret = get_sc_services(pctx, pctx, &pctx->smartcard_services); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to get p11 allowed services %d[%s]", + ret, sss_strerror(ret)); + sss_log(SSS_LOG_ERR, + "Failed to evaluate pam_p11_allowed_services option, " + "please check for typos in the SSSD configuration"); + return false; + } + } + + for (c = 0; pctx->smartcard_services[c] != NULL; c++) { + if (strcmp(pd->service, pctx->smartcard_services[c]) == 0) { + break; + } + } + if (pctx->smartcard_services[c] == NULL) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Smartcard authentication for service [%s] not supported.\n", + pd->service); + return false; + } + + return true; +} + +static errno_t get_p11_child_write_buffer(TALLOC_CTX *mem_ctx, + struct pam_data *pd, + uint8_t **_buf, size_t *_len) +{ + int ret; + uint8_t *buf; + size_t len; + const char *pin = NULL; + + if (pd == NULL || pd->authtok == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing authtok.\n"); + return EINVAL; + } + + switch (sss_authtok_get_type(pd->authtok)) { + case SSS_AUTHTOK_TYPE_SC_PIN: + ret = sss_authtok_get_sc_pin(pd->authtok, &pin, &len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_sc_pin failed.\n"); + return ret; + } + if (pin == NULL || len == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing PIN.\n"); + return EINVAL; + } + + buf = talloc_size(mem_ctx, len); + if (buf == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n"); + return ENOMEM; + } + + safealign_memcpy(buf, pin, len, NULL); + + break; + case SSS_AUTHTOK_TYPE_SC_KEYPAD: + /* Nothing to send */ + len = 0; + buf = NULL; + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported authtok type [%d].\n", + sss_authtok_get_type(pd->authtok)); + return EINVAL; + } + + *_len = len; + *_buf = buf; + + return EOK; +} + +static errno_t parse_p11_child_response(TALLOC_CTX *mem_ctx, uint8_t *buf, + ssize_t buf_len, + struct sss_certmap_ctx *sss_certmap_ctx, + struct cert_auth_info **_cert_list) +{ + int ret; + TALLOC_CTX *tmp_ctx = NULL; + uint8_t *p; + uint8_t *pn; + struct cert_auth_info *cert_list = NULL; + struct cert_auth_info *cert_auth_info; + unsigned char *der = NULL; + size_t der_size; + + if (buf_len <= 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Error occurred while reading data from p11_child (len = %ld)\n", + buf_len); + return EIO; + } + + p = memchr(buf, '\n', buf_len); + if ((p == NULL) || (p == buf)) { + DEBUG(SSSDBG_OP_FAILURE, "Missing status in p11_child response.\n"); + return EINVAL; + } + errno = 0; + ret = strtol((char *)buf, (char **)&pn, 10); + if ((errno != 0) || (pn == buf)) { + DEBUG(SSSDBG_OP_FAILURE, "Invalid status in p11_child response.\n"); + return EINVAL; + } + + if (ret != 0) { + if (ret == ERR_P11_PIN_LOCKED) { + DEBUG(SSSDBG_OP_FAILURE, "PIN locked\n"); + return ret; + } else { + *_cert_list = NULL; + return EOK; /* other errors are treated as "no cert found" */ + } + } + + if ((p - buf + 1) == buf_len) { + DEBUG(SSSDBG_TRACE_LIBS, "No certificate found.\n"); + *_cert_list = NULL; + return EOK; + } + + p++; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + do { + cert_auth_info = talloc_zero(tmp_ctx, struct cert_auth_info); + if (cert_auth_info == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n"); + ret = ENOMEM; + goto done; + } + + pn = memchr(p, '\n', buf_len - (p - buf)); + if (pn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Missing new-line in p11_child response.\n"); + ret = EINVAL; + goto done; + } + if (pn == p) { + DEBUG(SSSDBG_OP_FAILURE, + "Missing token name in p11_child response.\n"); + ret = EINVAL; + goto done; + } + + cert_auth_info->token_name = talloc_strndup(cert_auth_info, (char *)p, + (pn - p)); + if (cert_auth_info->token_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n"); + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_ALL, "Found token name [%s].\n", + cert_auth_info->token_name); + + p = ++pn; + pn = memchr(p, '\n', buf_len - (p - buf)); + if (pn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Missing new-line in p11_child response.\n"); + ret = EINVAL; + goto done; + } + + if (pn == p) { + DEBUG(SSSDBG_OP_FAILURE, + "Missing module name in p11_child response.\n"); + ret = EINVAL; + goto done; + } + + cert_auth_info->module_name = talloc_strndup(cert_auth_info, (char *)p, + (pn - p)); + if (cert_auth_info->module_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n"); + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_ALL, "Found module name [%s].\n", + cert_auth_info->module_name); + + p = ++pn; + pn = memchr(p, '\n', buf_len - (p - buf)); + if (pn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Missing new-line in p11_child response.\n"); + ret = EINVAL; + goto done; + } + + if (pn == p) { + DEBUG(SSSDBG_OP_FAILURE, + "Missing key id in p11_child response.\n"); + ret = EINVAL; + goto done; + } + + cert_auth_info->key_id = talloc_strndup(cert_auth_info, (char *)p, + (pn - p)); + if (cert_auth_info->key_id == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n"); + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_ALL, "Found key id [%s].\n", cert_auth_info->key_id); + + p = ++pn; + pn = memchr(p, '\n', buf_len - (p - buf)); + if (pn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Missing new-line in p11_child response.\n"); + ret = EINVAL; + goto done; + } + + if (pn == p) { + DEBUG(SSSDBG_OP_FAILURE, + "Missing label in p11_child response.\n"); + ret = EINVAL; + goto done; + } + + cert_auth_info->label = talloc_strndup(cert_auth_info, (char *) p, + (pn - p)); + if (cert_auth_info->label == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n"); + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_ALL, "Found label [%s].\n", cert_auth_info->label); + + p = ++pn; + pn = memchr(p, '\n', buf_len - (p - buf)); + if (pn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Missing new-line in p11_child response.\n"); + ret = EINVAL; + goto done; + } + + if (pn == p) { + DEBUG(SSSDBG_OP_FAILURE, "Missing cert in p11_child response.\n"); + ret = EINVAL; + goto done; + } + + cert_auth_info->cert = talloc_strndup(cert_auth_info, (char *)p, + (pn - p)); + if (cert_auth_info->cert == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n"); + ret = ENOMEM; + goto done; + } + DEBUG(SSSDBG_TRACE_ALL, "Found cert [%s].\n", cert_auth_info->cert); + + der = sss_base64_decode(tmp_ctx, cert_auth_info->cert, &der_size); + if (der == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sss_base64_decode failed.\n"); + ret = EIO; + goto done; + } + + ret = sss_certmap_match_cert(sss_certmap_ctx, der, der_size); + if (ret == 0) { + DLIST_ADD(cert_list, cert_auth_info); + } else { + DEBUG(SSSDBG_TRACE_LIBS, + "Cert [%s] does not match matching rules and is ignored.\n", + cert_auth_info->cert); + talloc_free(cert_auth_info); + } + + p = ++pn; + } while ((pn - buf) < buf_len); + + ret = EOK; + +done: + if (ret == EOK) { + DLIST_FOR_EACH(cert_auth_info, cert_list) { + talloc_steal(mem_ctx, cert_auth_info); + } + + *_cert_list = cert_list; + } + + talloc_free(tmp_ctx); + + return ret; +} + +struct pam_check_cert_state { + int child_status; + struct sss_child_ctx_old *child_ctx; + struct tevent_timer *timeout_handler; + struct tevent_context *ev; + struct sss_certmap_ctx *sss_certmap_ctx; + + struct child_io_fds *io; + + struct cert_auth_info *cert_list; + struct pam_data *pam_data; +}; + +static void p11_child_write_done(struct tevent_req *subreq); +static void p11_child_done(struct tevent_req *subreq); +static void p11_child_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt); + +struct tevent_req *pam_check_cert_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *ca_db, + time_t timeout, + const char *verify_opts, + struct sss_certmap_ctx *sss_certmap_ctx, + const char *uri, + struct pam_data *pd) +{ + errno_t ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct pam_check_cert_state *state; + pid_t child_pid; + struct timeval tv; + int pipefd_to_child[2] = PIPE_INIT; + int pipefd_from_child[2] = PIPE_INIT; + const char *extra_args[20] = { NULL }; + uint8_t *write_buf = NULL; + size_t write_buf_len = 0; + size_t arg_c; + uint64_t chain_id; + const char *module_name = NULL; + const char *token_name = NULL; + const char *key_id = NULL; + const char *label = NULL; + + req = tevent_req_create(mem_ctx, &state, struct pam_check_cert_state); + if (req == NULL) { + return NULL; + } + + if (ca_db == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing CA DB path.\n"); + ret = EINVAL; + goto done; + } + + if (sss_certmap_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing certificate matching context.\n"); + ret = EINVAL; + goto done; + } + + state->pam_data = pd; + + /* extra_args are added in revers order */ + arg_c = 0; + + chain_id = sss_chain_id_get(); + + extra_args[arg_c++] = talloc_asprintf(mem_ctx, "%lu", chain_id); + extra_args[arg_c++] = "--chain-id"; + + if (uri != NULL) { + DEBUG(SSSDBG_TRACE_ALL, "Adding PKCS#11 URI [%s].\n", uri); + extra_args[arg_c++] = uri; + extra_args[arg_c++] = "--uri"; + } + + if ((pd->cli_flags & PAM_CLI_FLAGS_REQUIRE_CERT_AUTH) && pd->priv == 1) { + extra_args[arg_c++] = "--wait_for_card"; + } + extra_args[arg_c++] = ca_db; + extra_args[arg_c++] = "--ca_db"; + if (verify_opts != NULL) { + extra_args[arg_c++] = verify_opts; + extra_args[arg_c++] = "--verify"; + } + + if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_SC_PIN + || sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_SC_KEYPAD) { + ret = sss_authtok_get_sc(pd->authtok, NULL, NULL, &token_name, NULL, + &module_name, NULL, &key_id, NULL, + &label, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_sc failed.\n"); + goto done; + } + + if (module_name != NULL && *module_name != '\0') { + extra_args[arg_c++] = module_name; + extra_args[arg_c++] = "--module_name"; + } + if (token_name != NULL && *token_name != '\0') { + extra_args[arg_c++] = token_name; + extra_args[arg_c++] = "--token_name"; + } + if (key_id != NULL && *key_id != '\0') { + extra_args[arg_c++] = key_id; + extra_args[arg_c++] = "--key_id"; + } + if (label != NULL && *label != '\0') { + extra_args[arg_c++] = label; + extra_args[arg_c++] = "--label"; + } + } + + if (pd->cmd == SSS_PAM_AUTHENTICATE) { + extra_args[arg_c++] = "--auth"; + switch (sss_authtok_get_type(pd->authtok)) { + case SSS_AUTHTOK_TYPE_SC_PIN: + extra_args[arg_c++] = "--pin"; + break; + case SSS_AUTHTOK_TYPE_SC_KEYPAD: + extra_args[arg_c++] = "--keypad"; + break; + default: + DEBUG(SSSDBG_OP_FAILURE, "Unsupported authtok type.\n"); + ret = EINVAL; + goto done; + } + + } else if (pd->cmd == SSS_PAM_PREAUTH) { + extra_args[arg_c++] = "--pre"; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected PAM command [%d].\n", pd->cmd); + ret = EINVAL; + goto done; + } + + state->ev = ev; + state->sss_certmap_ctx = sss_certmap_ctx; + state->child_status = EFAULT; + state->io = talloc(state, struct child_io_fds); + if (state->io == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc failed.\n"); + ret = ENOMEM; + goto done; + } + state->io->write_to_child_fd = -1; + state->io->read_from_child_fd = -1; + talloc_set_destructor((void *) state->io, child_io_destructor); + + ret = pipe(pipefd_from_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + ret = pipe(pipefd_to_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + + child_pid = fork(); + if (child_pid == 0) { /* child */ + exec_child_ex(state, pipefd_to_child, pipefd_from_child, + P11_CHILD_PATH, P11_CHILD_LOG_FILE, extra_args, false, + STDIN_FILENO, STDOUT_FILENO); + + /* We should never get here */ + DEBUG(SSSDBG_CRIT_FAILURE, "BUG: Could not exec p11 child\n"); + } else if (child_pid > 0) { /* parent */ + + state->io->read_from_child_fd = pipefd_from_child[0]; + PIPE_FD_CLOSE(pipefd_from_child[1]); + sss_fd_nonblocking(state->io->read_from_child_fd); + + state->io->write_to_child_fd = pipefd_to_child[1]; + PIPE_FD_CLOSE(pipefd_to_child[0]); + sss_fd_nonblocking(state->io->write_to_child_fd); + + /* Set up SIGCHLD handler */ + ret = child_handler_setup(ev, child_pid, NULL, NULL, &state->child_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not set up child handlers [%d]: %s\n", + ret, sss_strerror(ret)); + ret = ERR_P11_CHILD; + goto done; + } + + /* Set up timeout handler */ + tv = sss_tevent_timeval_current_ofs_time_t(timeout); + state->timeout_handler = tevent_add_timer(ev, req, tv, + p11_child_timeout, req); + if(state->timeout_handler == NULL) { + ret = ERR_P11_CHILD; + goto done; + } + + if (pd->cmd == SSS_PAM_AUTHENTICATE) { + ret = get_p11_child_write_buffer(state, pd, &write_buf, + &write_buf_len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "get_p11_child_write_buffer failed.\n"); + goto done; + } + } + + if (write_buf_len != 0) { + subreq = write_pipe_send(state, ev, write_buf, write_buf_len, + state->io->write_to_child_fd); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "write_pipe_send failed.\n"); + ret = ERR_P11_CHILD; + goto done; + } + tevent_req_set_callback(subreq, p11_child_write_done, req); + } else { + subreq = read_pipe_send(state, ev, state->io->read_from_child_fd); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "read_pipe_send failed.\n"); + ret = ERR_P11_CHILD; + goto done; + } + tevent_req_set_callback(subreq, p11_child_done, req); + } + + /* Now either wait for the timeout to fire or the child + * to finish + */ + } else { /* error */ + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "fork failed [%d][%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = EOK; + +done: + if (ret != EOK) { + PIPE_CLOSE(pipefd_from_child); + PIPE_CLOSE(pipefd_to_child); + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + return req; +} + +static void p11_child_write_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct pam_check_cert_state *state = tevent_req_data(req, + struct pam_check_cert_state); + int ret; + + ret = write_pipe_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + PIPE_FD_CLOSE(state->io->write_to_child_fd); + + subreq = read_pipe_send(state, state->ev, state->io->read_from_child_fd); + if (subreq == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, p11_child_done, req); +} + +static void p11_child_done(struct tevent_req *subreq) +{ + uint8_t *buf; + ssize_t buf_len; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct pam_check_cert_state *state = tevent_req_data(req, + struct pam_check_cert_state); + uint32_t user_info_type; + int ret; + + talloc_zfree(state->timeout_handler); + + ret = read_pipe_recv(subreq, state, &buf, &buf_len); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + PIPE_FD_CLOSE(state->io->read_from_child_fd); + + ret = parse_p11_child_response(state, buf, buf_len, state->sss_certmap_ctx, + &state->cert_list); + if (ret != EOK) { + if (ret == ERR_P11_PIN_LOCKED) { + DEBUG(SSSDBG_MINOR_FAILURE, "PIN locked\n"); + user_info_type = SSS_PAM_USER_INFO_PIN_LOCKED; + pam_add_response(state->pam_data, SSS_PAM_USER_INFO, + sizeof(uint32_t), (const uint8_t *) &user_info_type); + } else { + DEBUG(SSSDBG_OP_FAILURE, "parse_p11_child_response failed.\n"); + } + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +static void p11_child_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + struct pam_check_cert_state *state = + tevent_req_data(req, struct pam_check_cert_state); + + DEBUG(SSSDBG_CRIT_FAILURE, + "Timeout reached for p11_child, " + "consider increasing p11_child_timeout.\n"); + child_handler_destroy(state->child_ctx); + state->child_ctx = NULL; + state->child_status = ETIMEDOUT; + tevent_req_error(req, ERR_P11_CHILD_TIMEOUT); +} + +errno_t pam_check_cert_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + struct cert_auth_info **cert_list) +{ + struct cert_auth_info *tmp_cert_auth_info; + struct pam_check_cert_state *state = + tevent_req_data(req, struct pam_check_cert_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (cert_list != NULL) { + DLIST_FOR_EACH(tmp_cert_auth_info, state->cert_list) { + talloc_steal(mem_ctx, tmp_cert_auth_info); + } + + *cert_list = state->cert_list; + } + + return EOK; +} + +static char *get_cert_prompt(TALLOC_CTX *mem_ctx, + struct cert_auth_info *cert_info) +{ + int ret; + struct sss_certmap_ctx *ctx = NULL; + unsigned char *der = NULL; + size_t der_size; + char *prompt = NULL; + char *filter = NULL; + char **domains = NULL; + + ret = sss_certmap_init(mem_ctx, NULL, NULL, &ctx); + if (ret != 0) { + DEBUG(SSSDBG_OP_FAILURE, "sss_certmap_init failed.\n"); + return NULL; + } + + ret = sss_certmap_add_rule(ctx, 10, "KRB5:<ISSUER>.*", + "LDAP:{subject_dn!nss}", NULL); + if (ret != 0) { + DEBUG(SSSDBG_OP_FAILURE, "sss_certmap_add_rule failed.\n"); + goto done; + } + + der = sss_base64_decode(mem_ctx, sss_cai_get_cert(cert_info), &der_size); + if (der == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sss_base64_decode failed.\n"); + goto done; + } + + ret = sss_certmap_expand_mapping_rule(ctx, der, der_size, + &filter, &domains); + if (ret != 0) { + DEBUG(SSSDBG_OP_FAILURE, "sss_certmap_expand_mapping_rule failed.\n"); + goto done; + } + + prompt = talloc_asprintf(mem_ctx, "%s\n%s", sss_cai_get_label(cert_info), + filter); + if (prompt == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + } + +done: + sss_certmap_free_filter_and_domains(filter, domains); + sss_certmap_free_ctx(ctx); + talloc_free(der); + + return prompt; +} + +static errno_t pack_cert_data(TALLOC_CTX *mem_ctx, const char *sysdb_username, + struct cert_auth_info *cert_info, + const char *nss_name, + uint8_t **_msg, size_t *_msg_len) +{ + uint8_t *msg = NULL; + size_t msg_len; + const char *token_name; + const char *module_name; + const char *key_id; + const char *label; + char *prompt; + size_t user_len; + size_t token_len; + size_t module_len; + size_t key_id_len; + size_t label_len; + size_t prompt_len; + size_t nss_name_len; + const char *username = ""; + const char *nss_username = ""; + + if (sysdb_username != NULL) { + username = sysdb_username; + } + + if (nss_name != NULL) { + nss_username = nss_name; + } + + prompt = get_cert_prompt(mem_ctx, cert_info); + if (prompt == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "get_cert_prompt failed.\n"); + return EIO; + } + + token_name = sss_cai_get_token_name(cert_info); + module_name = sss_cai_get_module_name(cert_info); + key_id = sss_cai_get_key_id(cert_info); + label = sss_cai_get_label(cert_info); + + user_len = strlen(username) + 1; + token_len = strlen(token_name) + 1; + module_len = strlen(module_name) + 1; + key_id_len = strlen(key_id) + 1; + label_len = strlen(label) + 1; + prompt_len = strlen(prompt) + 1; + nss_name_len = strlen(nss_username) +1; + + msg_len = user_len + token_len + module_len + key_id_len + label_len + + prompt_len + nss_name_len; + + msg = talloc_zero_size(mem_ctx, msg_len); + if (msg == NULL) { + talloc_free(prompt); + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_size failed.\n"); + return ENOMEM; + } + + memcpy(msg, username, user_len); + memcpy(msg + user_len, token_name, token_len); + memcpy(msg + user_len + token_len, module_name, module_len); + memcpy(msg + user_len + token_len + module_len, key_id, key_id_len); + memcpy(msg + user_len + token_len + module_len + key_id_len, + label, label_len); + memcpy(msg + user_len + token_len + module_len + key_id_len + label_len, + prompt, prompt_len); + memcpy(msg + user_len + token_len + module_len + key_id_len + label_len + + prompt_len, + nss_username, nss_name_len); + talloc_free(prompt); + + if (_msg != NULL) { + *_msg = msg; + } + + if (_msg_len != NULL) { + *_msg_len = msg_len; + } + + return EOK; +} + +/* The PKCS11_LOGIN_TOKEN_NAME environment variable is e.g. used by the Gnome + * Settings Daemon to determine the name of the token used for login but it + * should be only set if SSSD is called by gdm-smartcard. Otherwise desktop + * components might assume that gdm-smartcard PAM stack is configured + * correctly which might not be the case e.g. if Smartcard authentication was + * used when running gdm-password. */ +#define PKCS11_LOGIN_TOKEN_ENV_NAME "PKCS11_LOGIN_TOKEN_NAME" + +errno_t add_pam_cert_response(struct pam_data *pd, struct sss_domain_info *dom, + const char *sysdb_username, + struct cert_auth_info *cert_info, + enum response_type type) +{ + uint8_t *msg = NULL; + char *env = NULL; + size_t msg_len; + int ret; + char *short_name = NULL; + char *domain_name = NULL; + const char *cert_info_name = sysdb_username; + struct sss_domain_info *user_dom; + char *nss_name = NULL; + + + if (type != SSS_PAM_CERT_INFO && type != SSS_PAM_CERT_INFO_WITH_HINT) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid response type [%d].\n", type); + return EINVAL; + } + + if ((type == SSS_PAM_CERT_INFO && sysdb_username == NULL) + || cert_info == NULL + || sss_cai_get_token_name(cert_info) == NULL + || sss_cai_get_module_name(cert_info) == NULL + || sss_cai_get_key_id(cert_info) == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing mandatory user or slot name.\n"); + return EINVAL; + } + + /* sysdb_username is a fully-qualified name which is used by pam_sss when + * prompting the user for the PIN and as login name if it wasn't set by + * the PAM caller but has to be determined based on the inserted + * Smartcard. If this type of name is irritating at the PIN prompt or the + * re_expression config option was set in a way that user@domain cannot be + * handled anymore some more logic has to be added here. But for the time + * being I think using sysdb_username is fine. + */ + + if (sysdb_username != NULL) { + ret = sss_parse_internal_fqname(pd, sysdb_username, + &short_name, &domain_name); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse name '%s' [%d]: %s, " + "using full name.\n", + sysdb_username, ret, sss_strerror(ret)); + } else { + if (domain_name != NULL) { + user_dom = find_domain_by_name(dom, domain_name, false); + + if (user_dom != NULL) { + ret = sss_output_fqname(short_name, user_dom, + sysdb_username, false, &nss_name); + if (ret != EOK) { + nss_name = NULL; + } + } + } + + } + } + + ret = pack_cert_data(pd, cert_info_name, cert_info, + nss_name != NULL ? nss_name : sysdb_username, + &msg, &msg_len); + talloc_free(short_name); + talloc_free(domain_name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "pack_cert_data failed.\n"); + return ret; + } + + ret = pam_add_response(pd, type, msg_len, msg); + talloc_free(msg); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "pam_add_response failed to add certificate info.\n"); + return ret; + } + + if (strcmp(pd->service, "gdm-smartcard") == 0) { + env = talloc_asprintf(pd, "%s=%s", PKCS11_LOGIN_TOKEN_ENV_NAME, + sss_cai_get_token_name(cert_info)); + if (env == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + return ENOMEM; + } + + ret = pam_add_response(pd, SSS_PAM_ENV_ITEM, strlen(env) + 1, + (uint8_t *)env); + talloc_free(env); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "pam_add_response failed to add environment variable.\n"); + return ret; + } + } + + return ret; +} diff --git a/src/responder/pam/pamsrv_passkey.c b/src/responder/pam/pamsrv_passkey.c new file mode 100644 index 0000000..1125840 --- /dev/null +++ b/src/responder/pam/pamsrv_passkey.c @@ -0,0 +1,1438 @@ +/* + SSSD + + PAM Responder - passkey related requests + + Copyright (C) Justin Stephenson <jstephen@redhat.com> 2022 + + 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 "util/child_common.h" +#include "util/authtok.h" +#include "db/sysdb.h" +#include "db/sysdb_passkey_user_verification.h" +#include "responder/pam/pamsrv.h" + +#include "responder/pam/pamsrv_passkey.h" + +struct pam_passkey_verification_enum_str { + enum passkey_user_verification verification; + const char *option; +}; + +struct pam_passkey_table_data { + hash_table_t *table; + char *key; + struct pk_child_user_data *data; +}; + +struct pam_passkey_verification_enum_str pam_passkey_verification_enum_str[] = { + { PAM_PASSKEY_VERIFICATION_ON, "on" }, + { PAM_PASSKEY_VERIFICATION_OFF, "off" }, + { PAM_PASSKEY_VERIFICATION_OMIT, "unset" }, + { PAM_PASSKEY_VERIFICATION_INVALID, NULL } +}; + +#define PASSKEY_PREFIX "passkey:" +#define USER_VERIFICATION "user_verification=" +#define USER_VERIFICATION_LEN (sizeof(USER_VERIFICATION) -1) + +const char *pam_passkey_verification_enum_to_string(enum passkey_user_verification verification) +{ + size_t c; + + for (c = 0 ; pam_passkey_verification_enum_str[c].option != NULL; c++) { + if (pam_passkey_verification_enum_str[c].verification == verification) { + return pam_passkey_verification_enum_str[c].option; + } + } + + return "(NULL)"; +} + +struct passkey_ctx { + struct pam_ctx *pam_ctx; + struct tevent_context *ev; + struct pam_data *pd; + struct pam_auth_req *preq; +}; + +void pam_forwarder_passkey_cb(struct tevent_req *req); + +errno_t pam_passkey_concatenate_keys(TALLOC_CTX *mem_ctx, + struct pk_child_user_data *pk_data, + bool kerberos_pa, + char **_result_kh, + char **_result_ph); + +struct tevent_req *pam_passkey_get_mapping_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct passkey_ctx *pctx); +void pam_passkey_get_user_done(struct tevent_req *req); +void pam_passkey_get_mapping_done(struct tevent_req *req); +errno_t pam_passkey_get_mapping_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct cache_req_result **_result); + +struct passkey_get_mapping_state { + struct pam_data *pd; + struct cache_req_result *result; +}; + +void passkey_kerberos_cb(struct tevent_req *req) +{ + struct pam_auth_req *preq = tevent_req_callback_data(req, + struct pam_auth_req); + errno_t ret = EOK; + int child_status; + + ret = pam_passkey_auth_recv(req, &child_status); + talloc_free(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "PAM passkey auth failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "passkey child finished with status [%d]\n", child_status); + + pam_check_user_search(preq); + +done: + pam_check_user_done(preq, ret); +} + +errno_t passkey_kerberos(struct pam_ctx *pctx, + struct pam_data *pd, + struct pam_auth_req *preq) +{ + errno_t ret; + const char *prompt; + const char *key; + const char *pin; + size_t pin_len; + struct pk_child_user_data *data; + struct tevent_req *req; + int timeout; + char *verify_opts; + bool debug_libfido2; + enum passkey_user_verification verification; + + ret = sss_authtok_get_passkey(preq, preq->pd->authtok, + &prompt, &key, &pin, &pin_len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failure to get passkey authtok\n"); + return EIO; + } + + if (prompt == NULL || key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Passkey prompt and key are missing or invalid.\n"); + return EIO; + } + + data = sss_ptr_hash_lookup(pctx->pk_table_data->table, key, + struct pk_child_user_data); + if (data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to lookup passkey authtok\n"); + return EIO; + } + + ret = confdb_get_int(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_PASSKEY_CHILD_TIMEOUT, PASSKEY_CHILD_TIMEOUT_DEFAULT, + &timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read passkey_child_timeout from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = confdb_get_string(pctx->rctx->cdb, preq, CONFDB_MONITOR_CONF_ENTRY, + CONFDB_MONITOR_PASSKEY_VERIFICATION, NULL, + &verify_opts); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read '"CONFDB_MONITOR_PASSKEY_VERIFICATION"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* Always use verification sent from passkey krb5 plugin */ + if (strcasecmp(data->user_verification, "false") == 0) { + verification = PAM_PASSKEY_VERIFICATION_OFF; + } else { + verification = PAM_PASSKEY_VERIFICATION_ON; + } + + ret = confdb_get_bool(pctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2, false, + &debug_libfido2); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read '"CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + req = pam_passkey_auth_send(preq->cctx, preq->cctx->ev, timeout, debug_libfido2, + verification, pd, data, true); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "passkey auth send failed [%d]: [%s]\n", + ret, sss_strerror(ret)); + goto done; + } + + tevent_req_set_callback(req, passkey_kerberos_cb, preq); + + ret = EAGAIN; + +done: + + return ret; + +} + + +errno_t passkey_local(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct pam_ctx *pam_ctx, + struct pam_auth_req *preq, + struct pam_data *pd) +{ + struct tevent_req *req; + struct passkey_ctx *pctx; + errno_t ret; + + pctx = talloc_zero(mem_ctx, struct passkey_ctx); + if (pctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "pctx == NULL\n"); + return ENOMEM; + } + + pctx->pd = pd; + pctx->pam_ctx = pam_ctx; + pctx->ev = ev; + pctx->preq = preq; + + DEBUG(SSSDBG_TRACE_FUNC, "Checking for passkey authentication data\n"); + + req = pam_passkey_get_mapping_send(mem_ctx, pctx->ev, pctx); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_get_mapping_send failed.\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(req, pam_passkey_get_user_done, pctx); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + talloc_free(pctx); + } + + return ret; +} + +struct tevent_req *pam_passkey_get_mapping_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct passkey_ctx *pk_ctx) +{ + + struct passkey_get_mapping_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + int ret; + static const char *attrs[] = { SYSDB_NAME, SYSDB_USER_PASSKEY, NULL }; + + req = tevent_req_create(mem_ctx, &state, struct passkey_get_mapping_state); + if (req == NULL) { + ret = ENOMEM; + goto done; + } + + subreq = cache_req_user_by_name_attrs_send(state, pk_ctx->ev, + pk_ctx->pam_ctx->rctx, + pk_ctx->pam_ctx->rctx->ncache, 0, + pk_ctx->pd->domain, + pk_ctx->pd->user, attrs); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, pam_passkey_get_mapping_done, req); + + return req; + +done: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + + return req; +} + +void pam_passkey_get_mapping_done(struct tevent_req *subreq) +{ + struct cache_req_result *result; + struct tevent_req *req; + struct passkey_get_mapping_state *state; + + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct passkey_get_mapping_state); + + ret = cache_req_user_by_name_attrs_recv(state, subreq, &result); + state->result = result; + + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +errno_t pam_passkey_get_mapping_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct cache_req_result **_result) +{ + struct passkey_get_mapping_state *state = NULL; + + state = tevent_req_data(req, struct passkey_get_mapping_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_result = talloc_steal(mem_ctx, state->result); + return EOK; +} + +errno_t read_passkey_conf_verification(TALLOC_CTX *mem_ctx, + const char *verify_opts, + enum passkey_user_verification *_user_verification) +{ + int ret; + TALLOC_CTX *tmp_ctx; + char **opts; + size_t c; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + if (verify_opts == NULL) { + ret = EOK; + goto done; + } + + ret = split_on_separator(tmp_ctx, verify_opts, ',', true, true, &opts, + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "split_on_separator failed [%d], %s.\n", + ret, sss_strerror(ret)); + goto done; + } + + for (c = 0; opts[c] != NULL; c++) { + if (strncasecmp(opts[c], USER_VERIFICATION, USER_VERIFICATION_LEN) == 0) { + if (strcasecmp("true", &opts[c][USER_VERIFICATION_LEN]) == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "user_verification set to true.\n"); + *_user_verification = PAM_PASSKEY_VERIFICATION_ON; + } else if (strcasecmp("false", &opts[c][USER_VERIFICATION_LEN]) == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "user_verification set to false.\n"); + *_user_verification = PAM_PASSKEY_VERIFICATION_OFF; + } + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "Unsupported passkey verification option [%s], " \ + "skipping.\n", opts[c]); + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t passkey_local_verification(TALLOC_CTX *mem_ctx, + struct passkey_ctx *pctx, + struct confdb_ctx *cdb, + struct sysdb_ctx *sysdb, + const char *domain_name, + struct pk_child_user_data *pk_data, + enum passkey_user_verification *_user_verification, + bool *_debug_libfido2) +{ + TALLOC_CTX *tmp_ctx; + errno_t ret; + const char *verification_from_ldap; + char *verify_opts = NULL; + bool debug_libfido2 = false; + enum passkey_user_verification verification = PAM_PASSKEY_VERIFICATION_OMIT; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sysdb_domain_get_passkey_user_verification(tmp_ctx, sysdb, domain_name, + &verification_from_ldap); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read passkeyUserVerification from sysdb: [%d]: %s\n", + ret, sss_strerror(ret)); + /* This is expected for AD and LDAP */ + ret = EOK; + goto done; + } + + ret = confdb_get_bool(pctx->pam_ctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2, false, + &debug_libfido2); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read '"CONFDB_PAM_PASSKEY_DEBUG_LIBFIDO2"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* If require user verification setting is set in LDAP, use it */ + if (verification_from_ldap != NULL) { + if (strcasecmp(verification_from_ldap, "true") == 0) { + verification = PAM_PASSKEY_VERIFICATION_ON; + } else if (strcasecmp(verification_from_ldap, "false") == 0) { + verification = PAM_PASSKEY_VERIFICATION_OFF; + } + DEBUG(SSSDBG_TRACE_FUNC, "Passkey verification is being enforced from LDAP\n"); + } else { + /* No verification set in LDAP, fallback to local sssd.conf setting */ + ret = confdb_get_string(pctx->pam_ctx->rctx->cdb, tmp_ctx, CONFDB_MONITOR_CONF_ENTRY, + CONFDB_MONITOR_PASSKEY_VERIFICATION, NULL, + &verify_opts); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read '"CONFDB_MONITOR_PASSKEY_VERIFICATION"' from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + + ret = read_passkey_conf_verification(tmp_ctx, verify_opts, &verification); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Unable to parse passkey verificaton options.\n"); + /* Continue anyway */ + } + DEBUG(SSSDBG_TRACE_FUNC, "Passkey verification is being enforced from local configuration\n"); + } + DEBUG(SSSDBG_TRACE_FUNC, "Passkey verification setting [%s]\n", + pam_passkey_verification_enum_to_string(verification)); + + *_user_verification = verification; + *_debug_libfido2 = debug_libfido2; + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t process_passkey_data(TALLOC_CTX *mem_ctx, + struct ldb_message *user_mesg, + const char *domain, + struct pk_child_user_data *_data) +{ + struct ldb_message_element *el; + TALLOC_CTX *tmp_ctx; + int ret; + char **mappings; + const char **kh_mappings; + const char **public_keys; + const char *domain_name; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ERROR("talloc_new() failed\n"); + return ENOMEM; + } + + el = ldb_msg_find_element(user_mesg, SYSDB_USER_PASSKEY); + if (el == NULL) { + DEBUG(SSSDBG_TRACE_FUNC, "No passkey data found\n"); + ret = ENOENT; + goto done; + } + + /* This attribute may contain other mapping data unrelated to passkey. In that case + * let's omit it. For example, AD user altSecurityIdentities may store ssh public key + * or smart card mapping data */ + ret = split_on_separator(tmp_ctx, (const char *) el->values[0].data, ':', true, true, + &mappings, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Incorrectly formatted passkey data [%d]: %s\n", + ret, sss_strerror(ret)); + ret = ENOENT; + goto done; + } else if (strcasecmp(mappings[0], "passkey") != 0) { + DEBUG(SSSDBG_TRACE_FUNC, "Mapping data found is not passkey related\n"); + ret = ENOENT; + goto done; + } + + kh_mappings = talloc_zero_array(tmp_ctx, const char *, el->num_values + 1); + if (kh_mappings == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + ret = ENOMEM; + goto done; + } + + public_keys = talloc_zero_array(tmp_ctx, const char *, el->num_values + 1); + if (public_keys == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + ret = ENOMEM; + goto done; + } + + for (int i = 0; i < el->num_values; i++) { + ret = split_on_separator(tmp_ctx, (const char *) el->values[i].data, ',', true, true, + &mappings, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Incorrectly formatted passkey data [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + kh_mappings[i] = talloc_strdup(kh_mappings, mappings[0] + strlen(PASSKEY_PREFIX)); + if (kh_mappings[i] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup key handle failed.\n"); + ret = ENOMEM; + goto done; + } + + public_keys[i] = talloc_strdup(public_keys, mappings[1]); + if (public_keys[i] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup public key failed.\n"); + ret = ENOMEM; + goto done; + } + } + + domain_name = talloc_strdup(tmp_ctx, domain); + if (domain_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup domain failed.\n"); + ret = ENOMEM; + goto done; + } + + _data->domain = talloc_steal(mem_ctx, domain_name); + _data->key_handles = talloc_steal(mem_ctx, kh_mappings); + _data->public_keys = talloc_steal(mem_ctx, public_keys); + _data->num_credentials = el->num_values; + + ret = EOK; +done: + talloc_free(tmp_ctx); + + return ret; +} + +void pam_forwarder_passkey_cb(struct tevent_req *req) +{ + struct pam_auth_req *preq = tevent_req_callback_data(req, + struct pam_auth_req); + errno_t ret = EOK; + int child_status; + + ret = pam_passkey_auth_recv(req, &child_status); + talloc_free(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "PAM passkey auth failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + preq->pd->passkey_local_done = true; + + DEBUG(SSSDBG_TRACE_FUNC, "passkey child finished with status [%d]\n", child_status); + preq->pd->pam_status = PAM_SUCCESS; + pam_reply(preq); + + return; + +done: + pam_check_user_done(preq, ret); +} + +void pam_passkey_get_user_done(struct tevent_req *req) +{ + int ret; + struct passkey_ctx *pctx; + bool debug_libfido2 = false; + char *domain_name = NULL; + int timeout; + struct cache_req_result *result = NULL; + struct pk_child_user_data *pk_data = NULL; + enum passkey_user_verification verification = PAM_PASSKEY_VERIFICATION_OMIT; + + pctx = tevent_req_callback_data(req, struct passkey_ctx); + + ret = pam_passkey_get_mapping_recv(pctx, req, &result); + talloc_zfree(req); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_OP_FAILURE, "cache_req_user_by_name_attrs_recv failed [%d]: %s.\n", + ret, sss_strerror(ret)); + goto done; + } + + if (result == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "cache req result == NULL\n"); + ret = ENOMEM; + goto done; + } + + pk_data = talloc_zero(pctx, struct pk_child_user_data); + if (!pk_data) { + DEBUG(SSSDBG_CRIT_FAILURE, "pk_data == NULL\n"); + ret = ENOMEM; + goto done; + } + + /* Use dns_name for AD/IPA - for LDAP fallback to domain->name */ + if (result->domain != NULL) { + domain_name = result->domain->dns_name; + if (domain_name == NULL) { + domain_name = result->domain->name; + } + } + + if (domain_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Invalid or missing domain name\n"); + ret = EIO; + goto done; + } + + /* Get passkey data */ + DEBUG(SSSDBG_TRACE_ALL, "Processing passkey data\n"); + ret = process_passkey_data(pk_data, result->msgs[0], domain_name, pk_data); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "process_passkey_data failed: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* timeout */ + ret = confdb_get_int(pctx->pam_ctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_PASSKEY_CHILD_TIMEOUT, PASSKEY_CHILD_TIMEOUT_DEFAULT, + &timeout); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to read passkey_child_timeout from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + ret = passkey_local_verification(pctx, pctx, pctx->pam_ctx->rctx->cdb, + result->domain->sysdb, result->domain->dns_name, + pk_data, &verification, &debug_libfido2); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to check passkey verification [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + /* Preauth respond with prompt_pin true or false based on user verification */ + if (pctx->pd->cmd == SSS_PAM_PREAUTH) { + const char *prompt_pin = verification == PAM_PASSKEY_VERIFICATION_OFF ? "false" : "true"; + + ret = pam_add_response(pctx->pd, SSS_PAM_PASSKEY_INFO, strlen(prompt_pin) + 1, + (const uint8_t *) prompt_pin); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed. [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + pctx->pd->pam_status = PAM_SUCCESS; + pam_reply(pctx->preq); + talloc_free(pk_data); + return; + } + + req = pam_passkey_auth_send(pctx, pctx->ev, timeout, debug_libfido2, + verification, pctx->pd, pk_data, false); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_auth_send failed [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + tevent_req_set_callback(req, pam_forwarder_passkey_cb, pctx->preq); + +done: + if (pk_data != NULL) { + talloc_free(pk_data); + } + + if (ret == ENOENT) { + /* No passkey data, continue through to typical auth flow */ + DEBUG(SSSDBG_TRACE_FUNC, "No passkey data found, skipping passkey auth\n"); + pctx->preq->passkey_data_exists = false; + pam_check_user_search(pctx->preq); + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unexpected passkey error [%d]: %s." + " Skipping passkey auth\n", + ret, sss_strerror(ret)); + pam_check_user_search(pctx->preq); + } + + return; +} + + +struct pam_passkey_auth_send_state { + struct pam_data *pd; + struct tevent_context *ev; + struct tevent_timer *timeout_handler; + struct sss_child_ctx_old *child_ctx; + struct child_io_fds *io; + const char *logfile; + const char **extra_args; + char *verify_opts; + int timeout; + int child_status; + bool kerberos_pa; +}; + +static errno_t passkey_child_exec(struct tevent_req *req); +static void pam_passkey_auth_done(int child_status, + struct tevent_signal *sige, + void *pvt); + +static int pin_destructor(void *ptr) +{ + uint8_t *pin = talloc_get_type(ptr, uint8_t); + if (pin == NULL) return EOK; + + sss_erase_talloc_mem_securely(pin); + + return EOK; +} + +errno_t get_passkey_child_write_buffer(TALLOC_CTX *mem_ctx, + struct pam_data *pd, + uint8_t **_buf, size_t *_len) +{ + int ret; + uint8_t *buf; + size_t len; + const char *pin = NULL; + + if (pd == NULL || pd->authtok == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing authtok.\n"); + return EINVAL; + } + + if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY || + sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_PASSKEY_KRB) { + ret = sss_authtok_get_passkey_pin(pd->authtok, &pin, &len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_passkey_pin failed [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + if (pin == NULL || len == 0) { + DEBUG(SSSDBG_OP_FAILURE, "Missing PIN.\n"); + return EINVAL; + } + + buf = talloc_size(mem_ctx, len); + if (buf == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n"); + return ENOMEM; + } + + talloc_set_destructor((void *) buf, pin_destructor); + + safealign_memcpy(buf, pin, len, NULL); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported authtok type [%d].\n", + sss_authtok_get_type(pd->authtok)); + return EINVAL; + } + + *_len = len; + *_buf = buf; + + return EOK; +} + +static void pam_passkey_child_read_data(struct tevent_req *subreq) +{ + uint8_t *buf; + ssize_t buf_len; + char *str; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct pam_passkey_auth_send_state *state = tevent_req_data(req, struct pam_passkey_auth_send_state); + int ret; + + ret = read_pipe_recv(subreq, state, &buf, &buf_len); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + str = malloc(sizeof(char) * buf_len); + if (str == NULL) { + return; + } + + snprintf(str, buf_len, "%s", buf); + + sss_authtok_set_passkey_reply(state->pd->authtok, str, 0); + + free(str); + + tevent_req_done(req); + return; +} + +static void passkey_child_write_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct pam_passkey_auth_send_state *state = tevent_req_data(req, struct pam_passkey_auth_send_state); + + int ret; + + DEBUG(SSSDBG_TRACE_LIBS, "Sending passkey data complete\n"); + + ret = write_pipe_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + PIPE_FD_CLOSE(state->io->write_to_child_fd); + + if (state->kerberos_pa) { + /* Read data back from passkey child */ + subreq = read_pipe_send(state, state->ev, state->io->read_from_child_fd); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "read_pipe_send failed.\n"); + return; + } + + tevent_req_set_callback(subreq, pam_passkey_child_read_data, req); + } +} + +errno_t pam_passkey_concatenate_keys(TALLOC_CTX *mem_ctx, + struct pk_child_user_data *pk_data, + bool kerberos_pa, + char **_result_kh, + char **_result_pk) +{ + errno_t ret; + char *result_kh = NULL; + char *result_pk = NULL; + + result_kh = talloc_strdup(mem_ctx, pk_data->key_handles[0]); + if (!kerberos_pa) { + result_pk = talloc_strdup(mem_ctx, pk_data->public_keys[0]); + } + + for (int i = 1; i < pk_data->num_credentials; i++) { + result_kh = talloc_strdup_append(result_kh, ","); + if (result_kh == NULL) { + ret = ENOMEM; + goto done; + } + + result_kh = talloc_strdup_append(result_kh, pk_data->key_handles[i]); + if (result_kh == NULL) { + ret = ENOMEM; + goto done; + } + + if (!kerberos_pa) { + result_pk = talloc_strdup_append(result_pk, ","); + if (result_pk == NULL) { + ret = ENOMEM; + goto done; + } + + result_pk = talloc_strdup_append(result_pk, pk_data->public_keys[i]); + if (result_kh == NULL || result_pk == NULL) { + ret = ENOMEM; + goto done; + } + } + } + + *_result_kh = result_kh; + *_result_pk = result_pk; + + ret = EOK; +done: + return ret; +} + +struct tevent_req * +pam_passkey_auth_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int timeout, + bool debug_libfido2, + enum passkey_user_verification verification, + struct pam_data *pd, + struct pk_child_user_data *pk_data, + bool kerberos_pa) +{ + struct tevent_req *req; + struct pam_passkey_auth_send_state *state; + size_t arg_c = 0; + char *result_kh; + char *result_pk; + int num_args; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct pam_passkey_auth_send_state); + if (req == NULL) { + return NULL; + } + + state->pd = pd; + state->ev = ev; + + state->timeout = timeout; + state->kerberos_pa = kerberos_pa; + state->logfile = PASSKEY_CHILD_LOG_FILE; + state->io = talloc(state, struct child_io_fds); + if (state->io == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc child fds failed.\n"); + ret = ENOMEM; + goto done; + } + state->io->write_to_child_fd = -1; + state->io->read_from_child_fd = -1; + talloc_set_destructor((void *) state->io, child_io_destructor); + + num_args = 11; + state->extra_args = talloc_zero_array(state, const char *, num_args + 1); + if (state->extra_args == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + ret = ENOMEM; + goto done; + } + + switch (verification) { + case PAM_PASSKEY_VERIFICATION_ON: + state->extra_args[arg_c++] = "--user-verification=true"; + DEBUG(SSSDBG_TRACE_FUNC, "Calling child with user-verification true\n"); + break; + case PAM_PASSKEY_VERIFICATION_OFF: + state->extra_args[arg_c++] = "--user-verification=false"; + DEBUG(SSSDBG_TRACE_FUNC, "Calling child with user-verification false\n"); + break; + default: + DEBUG(SSSDBG_TRACE_FUNC, "Calling child with user-verification unset\n"); + break; + } + + ret = pam_passkey_concatenate_keys(state, pk_data, state->kerberos_pa, + &result_kh, &result_pk); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "pam_passkey_concatenate keys failed - [%d]: [%s]\n", + ret, sss_strerror(ret)); + goto done; + } + + + if (state->kerberos_pa) { + state->extra_args[arg_c++] = pk_data->crypto_challenge; + state->extra_args[arg_c++] = "--cryptographic-challenge"; + state->extra_args[arg_c++] = result_kh; + state->extra_args[arg_c++] = "--key-handle"; + state->extra_args[arg_c++] = pk_data->domain; + state->extra_args[arg_c++] = "--domain"; + state->extra_args[arg_c++] = "--get-assert"; + } else { + state->extra_args[arg_c++] = result_pk; + state->extra_args[arg_c++] = "--public-key"; + state->extra_args[arg_c++] = result_kh; + state->extra_args[arg_c++] = "--key-handle"; + state->extra_args[arg_c++] = pk_data->domain; + state->extra_args[arg_c++] = "--domain"; + state->extra_args[arg_c++] = state->pd->user; + state->extra_args[arg_c++] = "--username"; + state->extra_args[arg_c++] = "--authenticate"; + } + + ret = passkey_child_exec(req); + +done: + if (ret == EOK) { + tevent_req_done(req); + tevent_req_post(req, ev); + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } + + return req; +} + +static void +passkey_child_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt) +{ + struct tevent_req *req = + talloc_get_type(pvt, struct tevent_req); + struct pam_passkey_auth_send_state *state = + tevent_req_data(req, struct pam_passkey_auth_send_state); + + DEBUG(SSSDBG_CRIT_FAILURE, "Timeout reached for passkey child, " + "consider increasing passkey_child_timeout\n"); + child_handler_destroy(state->child_ctx); + state->child_ctx = NULL; + state->child_status = ETIMEDOUT; + tevent_req_error(req, ERR_PASSKEY_CHILD_TIMEOUT); +} + +static errno_t passkey_child_exec(struct tevent_req *req) +{ + struct pam_passkey_auth_send_state *state; + struct tevent_req *subreq; + int pipefd_from_child[2] = PIPE_INIT; + int pipefd_to_child[2] = PIPE_INIT; + pid_t child_pid; + uint8_t *write_buf = NULL; + size_t write_buf_len = 0; + struct timeval tv; + int ret; + + state = tevent_req_data(req, struct pam_passkey_auth_send_state); + + ret = pipe(pipefd_from_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + ret = pipe(pipefd_to_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + + child_pid = fork(); + if (child_pid == 0) { /* child */ + exec_child_ex(state, pipefd_to_child, pipefd_from_child, + PASSKEY_CHILD_PATH, state->logfile, state->extra_args, + false, STDIN_FILENO, STDOUT_FILENO); + /* We should never get here */ + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "BUG: Could not exec passkey child\n"); + goto done; + } else if (child_pid > 0) { /* parent */ + state->io->read_from_child_fd = pipefd_from_child[0]; + PIPE_FD_CLOSE(pipefd_from_child[1]); + sss_fd_nonblocking(state->io->read_from_child_fd); + + state->io->write_to_child_fd = pipefd_to_child[1]; + PIPE_FD_CLOSE(pipefd_to_child[0]); + sss_fd_nonblocking(state->io->write_to_child_fd); + + /* Set up SIGCHLD handler */ + if (state->kerberos_pa) { + ret = child_handler_setup(state->ev, child_pid, NULL, req, &state->child_ctx); + } else { + ret = child_handler_setup(state->ev, child_pid, + pam_passkey_auth_done, req, + &state->child_ctx); + } + + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not set up child handlers [%d]: %s\n", + ret, sss_strerror(ret)); + ret = ERR_PASSKEY_CHILD; + goto done; + } + + /* Set up timeout handler */ + tv = tevent_timeval_current_ofs(state->timeout, 0); + state->timeout_handler = tevent_add_timer(state->ev, req, tv, + passkey_child_timeout, req); + if (state->timeout_handler == NULL) { + ret = ERR_PASSKEY_CHILD; + goto done; + } + + /* PIN is needed */ + if (sss_authtok_get_type(state->pd->authtok) != SSS_AUTHTOK_TYPE_EMPTY) { + ret = get_passkey_child_write_buffer(state, state->pd, &write_buf, + &write_buf_len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "get_passkey_child_write_buffer failed [%d]: %s.\n", + ret, sss_strerror(ret)); + goto done; + } + } + + if (write_buf_len != 0) { + subreq = write_pipe_send(state, state->ev, write_buf, write_buf_len, + state->io->write_to_child_fd); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "write_pipe_send failed.\n"); + ret = ERR_PASSKEY_CHILD; + goto done; + } + tevent_req_set_callback(subreq, passkey_child_write_done, req); + } + /* Now either wait for the timeout to fire or the child to finish */ + } else { /* error */ + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "fork failed [%d][%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + return EAGAIN; + +done: + if (ret != EOK) { + PIPE_CLOSE(pipefd_from_child); + PIPE_CLOSE(pipefd_to_child); + } + + return ret; +} + +errno_t pam_passkey_auth_recv(struct tevent_req *req, + int *child_status) +{ + struct pam_passkey_auth_send_state *state = + tevent_req_data(req, struct pam_passkey_auth_send_state); + + *child_status = state->child_status; + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +errno_t decode_pam_passkey_msg(TALLOC_CTX *mem_ctx, + uint8_t *buf, + size_t len, + struct pk_child_user_data **_data) +{ + + size_t p = 0; + size_t pctr = 0; + errno_t ret; + size_t offset; + struct pk_child_user_data *data = NULL; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + data = talloc_zero(tmp_ctx, struct pk_child_user_data); + if (data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to talloc passkey data.\n"); + ret = ENOMEM; + goto done; + } + + data->user_verification = talloc_strdup(data, (char *) &buf[p]); + if (data->user_verification == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to strdup passkey prompt.\n"); + ret = ENOMEM; + goto done; + } + + offset = strlen(data->user_verification) + 1; + if (offset >= len) { + DEBUG(SSSDBG_OP_FAILURE, "passkey prompt offset failure.\n"); + ret = EIO; + goto done; + } + + data->crypto_challenge = talloc_strdup(data, (char *) &buf[p + offset]); + if (data->crypto_challenge == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to strdup passkey challenge.\n"); + ret = ENOMEM; + goto done; + } + + offset += strlen(data->crypto_challenge) + 1; + if (offset >= len) { + DEBUG(SSSDBG_OP_FAILURE, "passkey challenge offset failure.\n"); + ret = EIO; + goto done; + } + + + data->domain = talloc_strdup(data, (char *) &buf[p] + offset); + if (data->domain == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to strdup passkey domain.\n"); + ret = ENOMEM; + goto done; + } + + offset += strlen(data->domain) + 1; + if (offset >= len) { + DEBUG(SSSDBG_OP_FAILURE, "passkey domain offset failure.\n"); + ret = EIO; + goto done; + } + + SAFEALIGN_COPY_UINT32(&data->num_credentials, &buf[p + offset], &pctr); + size_t list_sz = (size_t) data->num_credentials; + + offset += sizeof(uint32_t); + + data->key_handles = talloc_zero_array(data, const char *, list_sz); + + for (int i = 0; i < list_sz; i++) { + data->key_handles[i] = talloc_strdup(data->key_handles, (char *) &buf[p + offset]); + if (data->key_handles[i] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to strdup passkey list.\n"); + ret = ENOMEM; + goto done; + } + + offset += strlen(data->key_handles[i]) + 1; + } + + *_data = talloc_steal(mem_ctx, data); + + ret = EOK; +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t save_passkey_data(TALLOC_CTX *mem_ctx, + struct pam_ctx *pctx, + struct pk_child_user_data *data, + struct pam_auth_req *preq) +{ + char *pk_key; + errno_t ret; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + /* Passkey data (pk_table_data) is stolen onto client ctx, it will + * be freed when the client closes, and the sss_ptr_hash interface + * takes care of automatically removing it from the hash table then */ + pctx->pk_table_data = talloc_zero(tmp_ctx, struct pam_passkey_table_data); + if (pctx->pk_table_data == NULL) { + return ENOMEM; + } + + if (pctx->pk_table_data->table == NULL) { + pctx->pk_table_data->table = sss_ptr_hash_create(pctx->pk_table_data, + NULL, NULL); + if (pctx->pk_table_data->table == NULL) { + ret = ENOMEM; + goto done; + } + } + + pk_key = talloc_asprintf(tmp_ctx, "%s", data->crypto_challenge); + if (pk_key == NULL) { + ret = ENOMEM; + goto done; + } + + pctx->pk_table_data->key = talloc_strdup(pctx->pk_table_data, pk_key); + if (pctx->pk_table_data->key == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sss_ptr_hash_add(pctx->pk_table_data->table, pk_key, data, + struct pk_child_user_data); + if (ret == EEXIST) { + DEBUG(SSSDBG_TRACE_FUNC, "pk_table key [%s] already exists\n", + pk_key); + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to add pk data to hash table " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + talloc_steal(mem_ctx, pctx->pk_table_data); + pctx->pk_table_data->data = talloc_steal(mem_ctx, data); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t pam_eval_passkey_response(struct pam_ctx *pctx, + struct pam_data *pd, + struct pam_auth_req *preq, + bool *_pk_preauth_done) +{ + struct response_data *pk_resp; + struct pk_child_user_data *pk_data; + errno_t ret; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + pk_resp = pd->resp_list; + + while (pk_resp != NULL) { + switch (pk_resp->type) { + case SSS_PAM_PASSKEY_KRB_INFO: + if (!pctx->passkey_auth) { + /* Passkey auth is disabled. To avoid passkey prompts appearing, + * don't send SSS_PAM_PASSKEY_KRB_INFO to the client and + * add a dummy response to fallback to normal auth */ + pk_resp->do_not_send_to_client = true; + ret = pam_add_response(pd, SSS_OTP, 0, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + goto done; + } + break; + } + ret = decode_pam_passkey_msg(tmp_ctx, pk_resp->data, pk_resp->len, &pk_data); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to decode passkey msg\n"); + ret = EIO; + goto done; + } + + ret = save_passkey_data(preq->cctx, pctx, pk_data, preq); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to save passkey msg\n"); + ret = EIO; + goto done; + } + break; + /* Passkey non-kerberos preauth has already run */ + case SSS_PAM_PASSKEY_INFO: + *_pk_preauth_done = true; + default: + break; + } + pk_resp = pk_resp->next; + } + + ret = EOK; +done: + talloc_free(tmp_ctx); + + return ret; +} + + +static void +pam_passkey_auth_done(int child_status, + struct tevent_signal *sige, + void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + + struct pam_passkey_auth_send_state *state = + tevent_req_data(req, struct pam_passkey_auth_send_state); + state->child_status = WEXITSTATUS(child_status); + if (WIFEXITED(child_status)) { + if (WEXITSTATUS(child_status) != 0) { + DEBUG(SSSDBG_OP_FAILURE, + PASSKEY_CHILD_PATH " failed with status [%d]. Check passkey_child" + " logs for more information.\n", + WEXITSTATUS(child_status)); + tevent_req_error(req, ERR_PASSKEY_CHILD); + return; + } + } else if (WIFSIGNALED(child_status)) { + DEBUG(SSSDBG_OP_FAILURE, + PASSKEY_CHILD_PATH " was terminated by signal [%d]. Check passkey_child" + " logs for more information.\n", + WTERMSIG(child_status)); + tevent_req_error(req, ECHILD); + return; + } + + DEBUG(SSSDBG_TRACE_FUNC, "passkey data is valid. Mark done\n"); + + tevent_req_done(req); + return; +} + +bool may_do_passkey_auth(struct pam_ctx *pctx, + struct pam_data *pd) +{ +#ifndef BUILD_PASSKEY + DEBUG(SSSDBG_TRACE_FUNC, "Passkey auth not possible, SSSD built without passkey support!\n"); + return false; +#else + if (!pctx->passkey_auth) { + return false; + } + + if (pd->cmd != SSS_PAM_PREAUTH && pd->cmd != SSS_PAM_AUTHENTICATE) { + return false; + } + + if (pd->service == NULL || *pd->service == '\0') { + return false; + } + + return true; +#endif /* BUILD_PASSKEY */ +} diff --git a/src/responder/pam/pamsrv_passkey.h b/src/responder/pam/pamsrv_passkey.h new file mode 100644 index 0000000..7c5a532 --- /dev/null +++ b/src/responder/pam/pamsrv_passkey.h @@ -0,0 +1,83 @@ +/* + Authors: + Justin Stephenson <jstephen@redhat.com> + + Copyright (C) 2022 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/>. +*/ + +#ifndef __PAMSRV_PASSKEY_H__ +#define __PAMSRV_PASSKEY_H__ + +#include <security/pam_appl.h> +#include "util/util.h" +#include "util/sss_ptr_hash.h" +#include "responder/common/responder.h" +#include "responder/common/cache_req/cache_req.h" +#include "responder/pam/pamsrv.h" +#include "lib/certmap/sss_certmap.h" + +enum passkey_user_verification { + PAM_PASSKEY_VERIFICATION_ON, + PAM_PASSKEY_VERIFICATION_OFF, + PAM_PASSKEY_VERIFICATION_OMIT, + PAM_PASSKEY_VERIFICATION_INVALID +}; + +errno_t passkey_local(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct pam_ctx *pam_ctx, + struct pam_auth_req *preq, + struct pam_data *pd); +errno_t passkey_kerberos(struct pam_ctx *pctx, + struct pam_data *pd, + struct pam_auth_req *preq); + +struct pk_child_user_data { + /* Both Kerberos and non-kerberos */ + const char *domain; + size_t num_credentials; + const char *user_verification; + const char **key_handles; + /* Kerberos PA only */ + const char *crypto_challenge; + /* Non-kerberos only */ + const char *user; + const char **public_keys; +}; + +errno_t read_passkey_conf_verification(TALLOC_CTX *mem_ctx, + const char *verify_opts, + enum passkey_user_verification *_user_verification); + +void pam_forwarder_passkey_cb(struct tevent_req *req); +struct tevent_req *pam_passkey_auth_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int timeout, + bool debug_libfido2, + enum passkey_user_verification verification, + struct pam_data *pd, + struct pk_child_user_data *pk_data, + bool kerberos_pa); +errno_t pam_passkey_auth_recv(struct tevent_req *req, + int *child_status); +errno_t pam_eval_passkey_response(struct pam_ctx *pctx, + struct pam_data *pd, + struct pam_auth_req *preq, + bool *_pk_preauth_done); +bool may_do_passkey_auth(struct pam_ctx *pctx, + struct pam_data *pd); + +#endif /* __PAMSRV_PASSKEY_H__ */ diff --git a/src/responder/ssh/ssh_cert_to_ssh_key.c b/src/responder/ssh/ssh_cert_to_ssh_key.c new file mode 100644 index 0000000..b8bc8b7 --- /dev/null +++ b/src/responder/ssh/ssh_cert_to_ssh_key.c @@ -0,0 +1,342 @@ +/* + SSSD - certificate handling utils + + Copyright (C) Sumit Bose <sbose@redhat.com> 2018 + + 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 "util/util.h" +#include "util/cert.h" +#include "util/crypto/sss_crypto.h" +#include "util/child_common.h" +#include "lib/certmap/sss_certmap.h" + +struct cert_to_ssh_key_state { + struct tevent_context *ev; + const char *logfile; + time_t timeout; + const char **extra_args; + const char **certs; + struct ldb_val *keys; + size_t cert_count; + size_t iter; + size_t valid_keys; + + struct sss_child_ctx_old *child_ctx; + struct tevent_timer *timeout_handler; + struct child_io_fds *io; +}; + +static errno_t cert_to_ssh_key_step(struct tevent_req *req); +static void cert_to_ssh_key_done(int child_status, + struct tevent_signal *sige, + void *pvt); + +struct tevent_req *cert_to_ssh_key_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *logfile, time_t timeout, + const char *ca_db, + struct sss_certmap_ctx *sss_certmap_ctx, + size_t cert_count, + struct ldb_val *bin_certs, + const char *verify_opts) +{ + struct tevent_req *req; + struct cert_to_ssh_key_state *state; + size_t arg_c; + size_t c; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct cert_to_ssh_key_state); + if (req == NULL) { + return NULL; + } + + if (ca_db == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing CA DB path.\n"); + ret = EINVAL; + goto done; + } + + state->ev = ev; + state->logfile = logfile; + state->timeout = timeout; + state->io = talloc(state, struct child_io_fds); + if (state->io == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc failed.\n"); + ret = ENOMEM; + goto done; + } + state->io->write_to_child_fd = -1; + state->io->read_from_child_fd = -1; + talloc_set_destructor((void *) state->io, child_io_destructor); + + state->keys = talloc_zero_array(state, struct ldb_val, cert_count); + if (state->keys == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + ret = ENOMEM; + goto done; + } + state->valid_keys = 0; + + state->extra_args = talloc_zero_array(state, const char *, 8); + if (state->extra_args == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + ret = ENOMEM; + goto done; + } + /* extra_args are added in revers order, base64 encoded certificate is + * added at 0 */ + arg_c = 1; + state->extra_args[arg_c++] = "--certificate"; + state->extra_args[arg_c++] = ca_db; + state->extra_args[arg_c++] = "--ca_db"; + if (verify_opts != NULL) { + state->extra_args[arg_c++] = verify_opts; + state->extra_args[arg_c++] = "--verify"; + } + state->extra_args[arg_c++] = "--verification"; + + state->certs = talloc_zero_array(state, const char *, cert_count); + if (state->certs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero_array failed.\n"); + ret = ENOMEM; + goto done; + } + + state->cert_count = 0; + for (c = 0; c < cert_count; c++) { + + if (sss_certmap_ctx != NULL) { + ret = sss_certmap_match_cert(sss_certmap_ctx, bin_certs[c].data, + bin_certs[c].length); + if (ret != 0) { + DEBUG(SSSDBG_TRACE_ALL, "Certificate does not match matching " + "rules and is ignored.\n"); + continue; + } + } + state->certs[state->cert_count] = sss_base64_encode(state->certs, + bin_certs[c].data, + bin_certs[c].length); + if (state->certs[state->cert_count] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sss_base64_encode failed.\n"); + ret = EINVAL; + goto done; + } + + state->cert_count++; + } + + state->iter = 0; + + ret = cert_to_ssh_key_step(req); + +done: + if (ret != EAGAIN) { + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + } + + return req; +} + +static void p11_child_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + struct cert_to_ssh_key_state *state = + tevent_req_data(req, struct cert_to_ssh_key_state); + + DEBUG(SSSDBG_MINOR_FAILURE, "Timeout reached for p11_child.\n"); + child_handler_destroy(state->child_ctx); + state->child_ctx = NULL; + tevent_req_error(req, ERR_P11_CHILD_TIMEOUT); +} + +static errno_t cert_to_ssh_key_step(struct tevent_req *req) +{ + struct cert_to_ssh_key_state *state = tevent_req_data(req, + struct cert_to_ssh_key_state); + int ret; + int pipefd_from_child[2] = PIPE_INIT; + int pipefd_to_child[2] = PIPE_INIT; + pid_t child_pid; + struct timeval tv; + + if (state->iter >= state->cert_count) { + return EOK; + } + + state->extra_args[0] = state->certs[state->iter]; + + ret = pipe(pipefd_from_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + ret = pipe(pipefd_to_child); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, + "pipe failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + + child_pid = fork(); + if (child_pid == 0) { /* child */ + exec_child_ex(state, pipefd_to_child, pipefd_from_child, P11_CHILD_PATH, + state->logfile, state->extra_args, false, + STDIN_FILENO, STDOUT_FILENO); + /* We should never get here */ + DEBUG(SSSDBG_CRIT_FAILURE, "BUG: Could not exec p11 child\n"); + } else if (child_pid > 0) { /* parent */ + + state->io->read_from_child_fd = pipefd_from_child[0]; + PIPE_FD_CLOSE(pipefd_from_child[1]); + sss_fd_nonblocking(state->io->read_from_child_fd); + + state->io->write_to_child_fd = pipefd_to_child[1]; + PIPE_FD_CLOSE(pipefd_to_child[0]); + sss_fd_nonblocking(state->io->write_to_child_fd); + + /* Set up SIGCHLD handler */ + ret = child_handler_setup(state->ev, child_pid, cert_to_ssh_key_done, + req, &state->child_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not set up child handlers [%d]: %s\n", + ret, sss_strerror(ret)); + ret = ERR_P11_CHILD; + goto done; + } + + /* Set up timeout handler */ + tv = sss_tevent_timeval_current_ofs_time_t(state->timeout); + state->timeout_handler = tevent_add_timer(state->ev, req, tv, + p11_child_timeout, + req); + if (state->timeout_handler == NULL) { + ret = ERR_P11_CHILD; + goto done; + } + /* Now either wait for the timeout to fire or the child to finish */ + } else { /* error */ + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "fork failed [%d][%s].\n", + ret, sss_strerror(ret)); + goto done; + } + + return EAGAIN; + +done: + if (ret != EOK) { + PIPE_CLOSE(pipefd_from_child); + PIPE_CLOSE(pipefd_to_child); + } + + return ret; +} + +static void cert_to_ssh_key_done(int child_status, + struct tevent_signal *sige, + void *pvt) +{ + struct tevent_req *req = talloc_get_type(pvt, struct tevent_req); + struct cert_to_ssh_key_state *state = tevent_req_data(req, + struct cert_to_ssh_key_state); + int ret; + bool valid = false; + + PIPE_FD_CLOSE(state->io->read_from_child_fd); + PIPE_FD_CLOSE(state->io->write_to_child_fd); + + if (WIFEXITED(child_status)) { + if (WEXITSTATUS(child_status) != 0) { + DEBUG(SSSDBG_OP_FAILURE, + P11_CHILD_PATH " failed with status [%d]\n", child_status); + } else { + valid = true; + } + } + + if (WIFSIGNALED(child_status)) { + DEBUG(SSSDBG_OP_FAILURE, + P11_CHILD_PATH " was terminated by signal [%d]\n", + WTERMSIG(child_status)); + } + + if (valid) { + DEBUG(SSSDBG_TRACE_LIBS, "Certificate [%s] is valid.\n", + state->certs[state->iter]); + ret = get_ssh_key_from_derb64(state->keys, + state->certs[state->iter], + &state->keys[state->iter].data, + &state->keys[state->iter].length); + if (ret == EOK) { + state->valid_keys++; + } else { + DEBUG(SSSDBG_OP_FAILURE, "get_ssh_key_from_cert failed, " + "skipping certificate [%s].\n", + state->certs[state->iter]); + state->keys[state->iter].data = NULL; + state->keys[state->iter].length = 0; + } + } else { + DEBUG(SSSDBG_MINOR_FAILURE, "Certificate [%s] is not valid.\n", + state->certs[state->iter]); + state->keys[state->iter].data = NULL; + state->keys[state->iter].length = 0; + } + + state->iter++; + ret = cert_to_ssh_key_step(req); + + if (ret != EAGAIN) { + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + } + + return; +} + +errno_t cert_to_ssh_key_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + struct ldb_val **keys, size_t *valid_keys) +{ + struct cert_to_ssh_key_state *state = tevent_req_data(req, + struct cert_to_ssh_key_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (keys != NULL) { + *keys = talloc_steal(mem_ctx, state->keys); + } + + if (valid_keys != NULL) { + *valid_keys = state->valid_keys; + } + + return EOK; +} diff --git a/src/responder/ssh/ssh_cmd.c b/src/responder/ssh/ssh_cmd.c new file mode 100644 index 0000000..45ab57b --- /dev/null +++ b/src/responder/ssh/ssh_cmd.c @@ -0,0 +1,409 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 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 "config.h" + +#include <talloc.h> +#include <string.h> +#include <pwd.h> + +#include "db/sysdb.h" +#include "util/util.h" +#include "responder/common/responder.h" +#include "responder/common/cache_req/cache_req.h" +#include "responder/ssh/ssh_private.h" +#include "responder/pam/pam_helpers.h" +#include "lib/certmap/sss_certmap.h" + +struct ssh_cmd_ctx { + struct cli_ctx *cli_ctx; + const char *name; + const char *alias; + const char *domain; +}; + +static errno_t +ssh_check_non_sssd_user(const char *username) +{ + struct passwd *pwd; + + pwd = getpwnam(username); + if (pwd != NULL) { + DEBUG(SSSDBG_TRACE_ALL, "%s is a non-SSSD user\n", username); + return ERR_NON_SSSD_USER; + } + + return ENOENT; +} + + +static struct sss_domain_info * +ssh_get_result_domain(struct resp_ctx *rctx, + struct cache_req_result *result, + const char *name) +{ + if (result != NULL) { + return result->domain; + } + + return find_domain_by_name(rctx->domains, name, true); +} + +static void ssh_cmd_get_user_pubkeys_done(struct tevent_req *subreq); + +static errno_t ssh_cmd_get_user_pubkeys(struct cli_ctx *cli_ctx) +{ + struct ssh_cmd_ctx *cmd_ctx; + struct tevent_req *subreq; + errno_t ret; + + static const char *attrs[] = { SYSDB_NAME, SYSDB_SSH_PUBKEY, + SYSDB_USER_CERT, NULL }; + + cmd_ctx = talloc_zero(cli_ctx, struct ssh_cmd_ctx); + if (cmd_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + cmd_ctx->cli_ctx = cli_ctx; + + ret = ssh_protocol_parse_user(cli_ctx, cli_ctx->rctx->default_domain, + &cmd_ctx->name, &cmd_ctx->domain); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid request message!\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Requesting SSH user public keys for [%s] from [%s]\n", + cmd_ctx->name, cmd_ctx->domain ? cmd_ctx->domain : "<ALL>"); + + if (strcmp(cmd_ctx->name, "root") == 0) { + ret = ERR_NON_SSSD_USER; + goto done; + } + + subreq = cache_req_user_by_name_attrs_send(cmd_ctx, cli_ctx->ev, + cli_ctx->rctx, + cli_ctx->rctx->ncache, 0, + cmd_ctx->domain, + cmd_ctx->name, attrs); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ssh_cmd_get_user_pubkeys_done, cmd_ctx); + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(cmd_ctx); + return ssh_protocol_done(cli_ctx, ret); + } + + return ret; +} + +struct priv_sss_debug { + int level; +}; + +static void ssh_ext_debug(void *private, const char *file, long line, + const char *function, const char *format, ...) +{ + va_list ap; + struct priv_sss_debug *data = private; + int level = SSSDBG_OP_FAILURE; + + if (data != NULL) { + level = data->level; + } + + va_start(ap, format); + sss_vdebug_fn(file, line, function, level, APPEND_LINE_FEED, + format, ap); + va_end(ap); +} + +static errno_t ssh_cmd_refresh_certmap_ctx(struct ssh_ctx *ssh_ctx, + struct sss_domain_info *domains) +{ + + struct sss_certmap_ctx *sss_certmap_ctx = NULL; + struct sss_domain_info *dom; + struct certmap_info **certmap_list; + size_t c; + int ret; + bool rule_added; + bool all_rules = false; + bool no_rules = false; + bool rules_present = false; + + ssh_ctx->cert_rules_error = false; + + if (ssh_ctx->cert_rules == NULL || ssh_ctx->cert_rules[0] == NULL) { + all_rules = true; + } else if (ssh_ctx->cert_rules[0] != NULL + && ssh_ctx->cert_rules[1] == NULL) { + if (strcmp(ssh_ctx->cert_rules[0], "all_rules") == 0) { + all_rules = true; + } else if (strcmp(ssh_ctx->cert_rules[0], "no_rules") == 0) { + no_rules = true; + } + } + + if (!ssh_ctx->use_cert_keys + || ssh_ctx->certmap_last_read + >= ssh_ctx->rctx->get_domains_last_call.tv_sec + || no_rules) { + DEBUG(SSSDBG_TRACE_ALL, "No certmap update needed.\n"); + return EOK; + } + + ret = sss_certmap_init(ssh_ctx, ssh_ext_debug, NULL, &sss_certmap_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_certmap_init failed.\n"); + goto done; + } + + rule_added = false; + DLIST_FOR_EACH(dom, domains) { + certmap_list = dom->certmaps; + if (certmap_list == NULL || *certmap_list == NULL) { + continue; + } + + for (c = 0; certmap_list[c] != NULL; c++) { + rules_present = true; + + if (!all_rules && !string_in_list(certmap_list[c]->name, + ssh_ctx->cert_rules, true)) { + DEBUG(SSSDBG_TRACE_ALL, "Skipping matching rule [%s], it is " + "not listed in the ssh_use_certificate_matching_rules " + "option.\n", certmap_list[c]->name); + continue; + } + + DEBUG(SSSDBG_TRACE_ALL, + "Trying to add rule [%s][%d][%s][%s].\n", + certmap_list[c]->name, certmap_list[c]->priority, + certmap_list[c]->match_rule, certmap_list[c]->map_rule); + + ret = sss_certmap_add_rule(sss_certmap_ctx, + certmap_list[c]->priority, + certmap_list[c]->match_rule, + certmap_list[c]->map_rule, + certmap_list[c]->domains); + if (ret != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "sss_certmap_add_rule failed for rule [%s] " + "with error [%d][%s], skipping. " + "Please check for typos and if rule syntax is supported.\n", + certmap_list[c]->name, ret, sss_strerror(ret)); + continue; + } + rule_added = true; + } + } + + if (!rule_added) { + if (!rules_present) { + DEBUG(SSSDBG_TRACE_FUNC, + "No rules available, trying to add default matching rule.\n"); + ret = sss_certmap_add_rule(sss_certmap_ctx, SSS_CERTMAP_MIN_PRIO, + CERT_AUTH_DEFAULT_MATCHING_RULE, + NULL, NULL); + if (ret != 0) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to add default matching rule [%d][%s].\n", + ret, sss_strerror(ret)); + goto done; + } + } else { + DEBUG(SSSDBG_CONF_SETTINGS, + "No matching rule added, please check " + "ssh_use_certificate_matching_rules option values for " + "typos.\n"); + + ret = EINVAL; + goto done; + } + } + + ret = EOK; + +done: + if (ret == EOK) { + sss_certmap_free_ctx(ssh_ctx->sss_certmap_ctx); + ssh_ctx->sss_certmap_ctx = sss_certmap_ctx; + ssh_ctx->certmap_last_read = ssh_ctx->rctx->get_domains_last_call.tv_sec; + } else { + sss_certmap_free_ctx(sss_certmap_ctx); + ssh_ctx->cert_rules_error = true; + } + + return ret; +} + +static void ssh_cmd_get_user_pubkeys_done(struct tevent_req *subreq) +{ + struct cache_req_result *result; + struct ssh_cmd_ctx *cmd_ctx; + errno_t ret; + struct ssh_ctx *ssh_ctx; + + cmd_ctx = tevent_req_callback_data(subreq, struct ssh_cmd_ctx); + + ret = cache_req_user_by_name_attrs_recv(cmd_ctx, subreq, &result); + talloc_zfree(subreq); + if (ret != EOK) { + if (ret == ENOENT) { + /* Check if it is a non SSSD user. */ + ret = ssh_check_non_sssd_user(cmd_ctx->name); + } + + ssh_protocol_done(cmd_ctx->cli_ctx, ret); + goto done; + } + + ssh_ctx = talloc_get_type(cmd_ctx->cli_ctx->rctx->pvt_ctx, struct ssh_ctx); + ret = ssh_cmd_refresh_certmap_ctx(ssh_ctx, cmd_ctx->cli_ctx->rctx->domains); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "ssh_cmd_refresh_certmap_ctx failed, " + "certificate matching might not work as expected.\n"); + } + + ssh_protocol_reply(cmd_ctx->cli_ctx, result); + +done: + talloc_free(cmd_ctx); +} + +static void ssh_cmd_get_host_pubkeys_done(struct tevent_req *subreq); + +static errno_t ssh_cmd_get_host_pubkeys(struct cli_ctx *cli_ctx) +{ + struct ssh_cmd_ctx *cmd_ctx; + struct tevent_req *subreq; + errno_t ret; + + static const char *attrs[] = { SYSDB_NAME, SYSDB_SSH_PUBKEY, NULL }; + + cmd_ctx = talloc_zero(cli_ctx, struct ssh_cmd_ctx); + if (cmd_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + cmd_ctx->cli_ctx = cli_ctx; + + ret = ssh_protocol_parse_host(cli_ctx, &cmd_ctx->name, &cmd_ctx->alias, + &cmd_ctx->domain); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid request message!\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Requesting SSH host public keys for [%s] from [%s]\n", + cmd_ctx->name, cmd_ctx->domain ? cmd_ctx->domain : "<ALL>"); + + subreq = cache_req_ssh_host_id_by_name_send(cmd_ctx, cli_ctx->ev, + cli_ctx->rctx, + cli_ctx->rctx->ncache, 0, + cmd_ctx->domain, + cmd_ctx->name, + cmd_ctx->alias, attrs); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, ssh_cmd_get_host_pubkeys_done, cmd_ctx); + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(cmd_ctx); + return ssh_protocol_done(cli_ctx, ret); + } + + return ret; +} + +static void ssh_cmd_get_host_pubkeys_done(struct tevent_req *subreq) +{ + struct cache_req_result *result = NULL; + struct sss_domain_info *domain; + struct ssh_cmd_ctx *cmd_ctx; + struct ssh_ctx *ssh_ctx; + errno_t ret; + + cmd_ctx = tevent_req_callback_data(subreq, struct ssh_cmd_ctx); + ssh_ctx = talloc_get_type(cmd_ctx->cli_ctx->rctx->pvt_ctx, struct ssh_ctx); + + ret = cache_req_ssh_host_id_by_name_recv(cmd_ctx, subreq, &result); + talloc_zfree(subreq); + + if (ret == EOK || ret == ENOENT) { + domain = ssh_get_result_domain(ssh_ctx->rctx, result, cmd_ctx->domain); + + ssh_update_known_hosts_file(ssh_ctx->rctx->domains, domain, + cmd_ctx->name, ssh_ctx->hash_known_hosts, + ssh_ctx->known_hosts_timeout); + } + + if (ret != EOK) { + ssh_protocol_done(cmd_ctx->cli_ctx, ret); + goto done; + } + + ssh_protocol_reply(cmd_ctx->cli_ctx, result); + +done: + talloc_free(cmd_ctx); +} + +struct cli_protocol_version *register_cli_protocol_version(void) +{ + static struct cli_protocol_version ssh_cli_protocol_version[] = { + {0, NULL, NULL} + }; + + return ssh_cli_protocol_version; +} + +struct sss_cmd_table *get_ssh_cmds(void) { + static struct sss_cmd_table ssh_cmds[] = { + {SSS_GET_VERSION, sss_cmd_get_version}, + {SSS_SSH_GET_USER_PUBKEYS, ssh_cmd_get_user_pubkeys}, + {SSS_SSH_GET_HOST_PUBKEYS, ssh_cmd_get_host_pubkeys}, + {SSS_CLI_NULL, NULL} + }; + + return ssh_cmds; +} diff --git a/src/responder/ssh/ssh_known_hosts.c b/src/responder/ssh/ssh_known_hosts.c new file mode 100644 index 0000000..2be240b --- /dev/null +++ b/src/responder/ssh/ssh_known_hosts.c @@ -0,0 +1,333 @@ +/* + Authors: + Jan Cholasta <jcholast@redhat.com> + + Copyright (C) 2012 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 "config.h" + +#include <talloc.h> + +#include "util/util.h" +#include "util/crypto/sss_crypto.h" +#include "util/sss_ssh.h" +#include "db/sysdb.h" +#include "db/sysdb_ssh.h" +#include "responder/ssh/ssh_private.h" + +static char * +ssh_host_pubkeys_format_known_host_plain(TALLOC_CTX *mem_ctx, + struct sss_ssh_ent *ent) +{ + TALLOC_CTX *tmp_ctx; + errno_t ret; + char *name, *pubkey; + char *result = NULL; + size_t i; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return NULL; + } + + name = talloc_strdup(tmp_ctx, ent->name); + if (!name) { + goto done; + } + + for (i = 0; i < ent->num_aliases; i++) { + name = talloc_asprintf_append(name, ",%s", ent->aliases[i]); + if (!name) { + goto done; + } + } + + result = talloc_strdup(tmp_ctx, ""); + if (!result) { + goto done; + } + + for (i = 0; i < ent->num_pubkeys; i++) { + ret = sss_ssh_format_pubkey(tmp_ctx, &ent->pubkeys[i], &pubkey); + if (ret != EOK) { + result = NULL; + goto done; + } + + result = talloc_asprintf_append(result, "%s %s\n", name, pubkey); + if (!result) { + goto done; + } + + talloc_free(pubkey); + } + + talloc_steal(mem_ctx, result); + +done: + talloc_free(tmp_ctx); + + return result; +} + +static char * +ssh_host_pubkeys_format_known_host_hashed(TALLOC_CTX *mem_ctx, + struct sss_ssh_ent *ent) +{ + TALLOC_CTX *tmp_ctx; + errno_t ret; + char *name, *pubkey, *saltstr, *hashstr, *result; + unsigned char salt[SSS_SHA1_LENGTH], hash[SSS_SHA1_LENGTH]; + size_t i, j; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return NULL; + } + + result = talloc_strdup(tmp_ctx, ""); + if (!result) { + goto done; + } + + for (i = 0; i < ent->num_pubkeys; i++) { + ret = sss_ssh_format_pubkey(tmp_ctx, &ent->pubkeys[i], &pubkey); + if (ret != EOK) { + result = NULL; + goto done; + } + + for (j = 0; j <= ent->num_aliases; j++) { + name = (j == 0 ? ent->name : ent->aliases[j-1]); + + ret = sss_generate_csprng_buffer((uint8_t *)salt, SSS_SHA1_LENGTH); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_generate_csprng_buffer() failed (%d)\n", ret); + result = NULL; + goto done; + } + + ret = sss_hmac_sha1(salt, SSS_SHA1_LENGTH, + (unsigned char *)name, strlen(name), + hash); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_hmac_sha1() failed (%d): %s\n", + ret, strerror(ret)); + result = NULL; + goto done; + } + + saltstr = sss_base64_encode(tmp_ctx, salt, SSS_SHA1_LENGTH); + if (!saltstr) { + result = NULL; + goto done; + } + + hashstr = sss_base64_encode(tmp_ctx, hash, SSS_SHA1_LENGTH); + if (!hashstr) { + result = NULL; + goto done; + } + + result = talloc_asprintf_append(result, "|1|%s|%s %s\n", + saltstr, hashstr, pubkey); + if (!result) { + goto done; + } + + talloc_free(saltstr); + talloc_free(hashstr); + } + + talloc_free(pubkey); + } + + talloc_steal(mem_ctx, result); + +done: + talloc_free(tmp_ctx); + + return result; +} + +static errno_t +ssh_write_known_hosts(struct sss_domain_info *domains, + bool hash_known_hosts, + time_t now, + int fd) +{ + TALLOC_CTX *tmp_ctx; + struct sss_domain_info *dom; + struct ldb_message **hosts; + struct sysdb_ctx *sysdb; + struct sss_ssh_ent *ent; + char *entstr; + size_t num_hosts; + size_t i; + ssize_t wret; + errno_t ret; + + static const char *attrs[] = { + SYSDB_NAME, + SYSDB_NAME_ALIAS, + SYSDB_SSH_PUBKEY, + NULL + }; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + for (dom = domains; dom != NULL; dom = get_next_domain(dom, false)) { + sysdb = dom->sysdb; + if (sysdb == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Fatal: Sysdb CTX not found for this domain!\n"); + ret = EFAULT; + goto done; + } + + ret = sysdb_get_ssh_known_hosts(tmp_ctx, dom, now, attrs, + &hosts, &num_hosts); + if (ret == ENOENT) { + continue; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Host search failed for domain " + "%s [%d]: %s\n", dom->name, ret, sss_strerror(ret)); + continue; + } + + for (i = 0; i < num_hosts; i++) { + ret = sss_ssh_make_ent(tmp_ctx, hosts[i], &ent); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "Failed to get SSH host public keys\n"); + continue; + } + + if (hash_known_hosts) { + entstr = ssh_host_pubkeys_format_known_host_hashed(ent, ent); + } else { + entstr = ssh_host_pubkeys_format_known_host_plain(ent, ent); + } + + if (entstr == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to format known_hosts data " + "for [%s]\n", ent->name); + continue; + } + + wret = sss_atomic_write_s(fd, entstr, strlen(entstr)); + if (wret == -1) { + ret = errno; + goto done; + } + + talloc_free(ent); + } + + talloc_free(hosts); + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +ssh_update_known_hosts_file(struct sss_domain_info *domains, + struct sss_domain_info *domain, + const char *name, + bool hash_known_hosts, + int known_hosts_timeout) +{ + TALLOC_CTX *tmp_ctx; + char *filename; + errno_t ret; + time_t now; + int fd = -1; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + now = time(NULL); + + /* Update host's expiration time. */ + if (domain != NULL) { + ret = sysdb_update_ssh_known_host_expire(domain, name, now, + known_hosts_timeout); + if (ret != EOK && ret != ENOENT) { + goto done; + } + } + + /* Create temporary known hosts file. */ + filename = talloc_strdup(tmp_ctx, SSS_SSH_KNOWN_HOSTS_TEMP_TMPL); + if (filename == NULL) { + ret = ENOMEM; + goto done; + } + + fd = sss_unique_file_ex(tmp_ctx, filename, 0133, &ret); + if (fd == -1) { + filename = NULL; + goto done; + } + + /* Write contents. */ + ret = ssh_write_known_hosts(domains, hash_known_hosts, now, fd); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to write known hosts file " + "[%d]: %s\n", ret, sss_strerror(ret)); + goto done; + } + + + /* Rename to SSH known hosts file. */ + ret = fchmod(fd, 0644); + if (ret == -1) { + ret = errno; + goto done; + } + + ret = rename(filename, SSS_SSH_KNOWN_HOSTS_PATH); + if (ret == -1) { + ret = errno; + goto done; + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + if (fd != -1) { + close(fd); + } + + return ret; +} diff --git a/src/responder/ssh/ssh_private.h b/src/responder/ssh/ssh_private.h new file mode 100644 index 0000000..0e4ed10 --- /dev/null +++ b/src/responder/ssh/ssh_private.h @@ -0,0 +1,100 @@ +/* + Authors: + Jan Cholasta <jcholast@redhat.com> + + Copyright (C) 2012 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/>. +*/ + +#ifndef _SSHSRV_PRIVATE_H_ +#define _SSHSRV_PRIVATE_H_ + +#include "responder/common/responder.h" +#include "responder/common/cache_req/cache_req.h" + +#define SSS_SSH_KNOWN_HOSTS_PATH PUBCONF_PATH"/known_hosts" +#define SSS_SSH_KNOWN_HOSTS_TEMP_TMPL PUBCONF_PATH"/.known_hosts.XXXXXX" + +struct ssh_ctx { + struct resp_ctx *rctx; + struct sss_names_ctx *snctx; + + bool hash_known_hosts; + int known_hosts_timeout; + char *ca_db; + bool use_cert_keys; + + time_t certmap_last_read; + struct sss_certmap_ctx *sss_certmap_ctx; + char **cert_rules; + bool cert_rules_error; +}; + +struct sss_cmd_table *get_ssh_cmds(void); + +errno_t +ssh_protocol_parse_user(struct cli_ctx *cli_ctx, + const char *default_domain, + const char **_name, + const char **_domain); + +errno_t +ssh_protocol_parse_host(struct cli_ctx *cli_ctx, + const char **_name, + const char **_alias, + const char **_domain); + +void ssh_protocol_reply(struct cli_ctx *cli_ctx, + struct cache_req_result *result); + +errno_t +ssh_protocol_done(struct cli_ctx *cli_ctx, errno_t error); + +struct tevent_req * ssh_get_output_keys_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct cli_ctx *cli_ctx, + struct sss_domain_info *domain, + struct ldb_message *msg); + +errno_t ssh_get_output_keys_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + struct sized_string *name, + struct ldb_message_element ***elements, + uint32_t *num_keys); + +errno_t +ssh_protocol_build_reply(struct sss_packet *packet, + struct sized_string name, + struct ldb_message_element **elements, + uint32_t num_keys); + +errno_t +ssh_update_known_hosts_file(struct sss_domain_info *domains, + struct sss_domain_info *domain, + const char *name, + bool hash_known_hosts, + int known_hosts_timeout); + +struct tevent_req *cert_to_ssh_key_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *logfile, time_t timeout, + const char *ca_db, + struct sss_certmap_ctx *sss_certmap_ctx, + size_t cert_count, + struct ldb_val *bin_certs, + const char *verify_opts); + +errno_t cert_to_ssh_key_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + struct ldb_val **keys, size_t *valid_keys); +#endif /* _SSHSRV_PRIVATE_H_ */ diff --git a/src/responder/ssh/ssh_protocol.c b/src/responder/ssh/ssh_protocol.c new file mode 100644 index 0000000..5a9081b --- /dev/null +++ b/src/responder/ssh/ssh_protocol.c @@ -0,0 +1,252 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2017 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 "config.h" + +#include <talloc.h> + +#include "util/util.h" +#include "util/sss_ssh.h" +#include "responder/common/responder.h" +#include "responder/common/responder_packet.h" +#include "responder/common/cache_req/cache_req.h" +#include "responder/ssh/ssh_private.h" + +errno_t +ssh_protocol_done(struct cli_ctx *cli_ctx, errno_t error) +{ + struct cli_protocol *pctx; + errno_t ret; + + pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol); + + switch (error) { + case EOK: + /* Create empty packet if none was provided. */ + if (pctx->creq->out == NULL) { + ret = sss_packet_new(pctx->creq, 0, + sss_packet_get_cmd(pctx->creq->in), + &pctx->creq->out); + if (ret != EOK) { + goto done; + } + + sss_packet_set_error(pctx->creq->out, EOK); + } + + DEBUG(SSSDBG_TRACE_ALL, "Sending reply: success\n"); + ret = EOK; + goto done; + default: + DEBUG(SSSDBG_TRACE_ALL, "Sending reply: error [%d]: %s\n", + error, sss_strerror(error)); + ret = sss_cmd_send_error(cli_ctx, error); + goto done; + } + +done: + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to send reply [%d]: %s!\n", + ret, sss_strerror(ret)); + return ret; + } + + sss_cmd_done(cli_ctx, NULL); + return EOK; +} + +static void got_ssh_keys(struct tevent_req *req); +void ssh_protocol_reply(struct cli_ctx *cli_ctx, + struct cache_req_result *result) +{ + errno_t ret; + struct tevent_req *req; + + /* Make sure we have the results around until the end of the request. To + * avoid copying and memory allocation the keys and certificates from the + * result will be referenced during the next requests, so they should not + * be freed too early. */ + result = talloc_steal(cli_ctx, result); + + req = ssh_get_output_keys_send(cli_ctx, cli_ctx->ev, cli_ctx, + result->domain, result->msgs[0]); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sss_get_output_keys_send failed.\n"); + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(req, got_ssh_keys, cli_ctx); + + return; + +done: + ssh_protocol_done(cli_ctx, ret); +} + +static void got_ssh_keys(struct tevent_req *req) +{ + errno_t ret; + struct cli_ctx *cli_ctx = tevent_req_callback_data(req, struct cli_ctx); + struct cli_protocol *pctx; + pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol); + struct ldb_message_element **elements; + uint32_t num_keys; + struct sized_string name; + + ret = ssh_get_output_keys_recv(req, cli_ctx, &name, &elements, &num_keys); + talloc_zfree(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_get_output_keys_revc failed"); + goto done; + } + + ret = sss_packet_new(pctx->creq, 0, sss_packet_get_cmd(pctx->creq->in), + &pctx->creq->out); + if (ret != EOK) { + goto done; + } + + ret = ssh_protocol_build_reply(pctx->creq->out, name, elements, num_keys); + if (ret != EOK) { + goto done; + } + + sss_packet_set_error(pctx->creq->out, EOK); + +done: + ssh_protocol_done(cli_ctx, ret); +} + +static errno_t +ssh_protocol_parse_request(struct cli_ctx *cli_ctx, + const char *default_domain, + const char **_name, + const char **_alias, + const char **_domain) +{ + struct cli_protocol *pctx; + const char *name = NULL; + const char *alias = NULL; + const char *domain = NULL; + uint32_t flags; + uint32_t name_len; + uint32_t alias_len; + uint32_t domain_len; + size_t body_len; + uint8_t *body; + size_t c = 0; + + pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol); + + sss_packet_get_body(pctx->creq->in, &body, &body_len); + + SAFEALIGN_COPY_UINT32_CHECK(&flags, body + c, body_len, &c); + if (flags & ~(uint32_t)SSS_SSH_REQ_MASK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid flags received [0x%x]\n", flags); + return EINVAL; + } + + SAFEALIGN_COPY_UINT32_CHECK(&name_len, body + c, body_len, &c); + if (name_len == 0 || name_len > body_len - c) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid name length\n"); + return EINVAL; + } + + name = (const char *)(body + c); + if (!sss_utf8_check((const uint8_t *)name, name_len-1) || + name[name_len - 1] != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Name is not valid UTF-8 string\n"); + return EINVAL; + } + c += name_len; + + if (flags & SSS_SSH_REQ_ALIAS) { + SAFEALIGN_COPY_UINT32_CHECK(&alias_len, body + c, body_len, &c); + if (alias_len == 0 || alias_len > body_len - c) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid alias length\n"); + return EINVAL; + } + + alias = (const char *)(body+c); + if (!sss_utf8_check((const uint8_t *)alias, alias_len - 1) || + alias[alias_len - 1] != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Alias is not valid UTF-8 string\n"); + return EINVAL; + } + c += alias_len; + } + + if (flags & SSS_SSH_REQ_DOMAIN) { + SAFEALIGN_COPY_UINT32_CHECK(&domain_len, body + c, body_len, &c); + if (domain_len > 0) { + if (domain_len > body_len - c) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid domain length\n"); + return EINVAL; + } + + domain = (const char *)(body + c); + if (!sss_utf8_check((const uint8_t *)domain, domain_len - 1) || + domain[domain_len - 1] != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Domain is not valid UTF-8 string\n"); + return EINVAL; + } + c += domain_len; + } else { + domain = default_domain; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Requested domain [%s]\n", domain ? domain : "<ALL>"); + } + + if (_name != NULL) { + *_name = name; + } + + if (_alias != NULL) { + *_alias = alias; + } + + if (_domain != NULL) { + *_domain = domain; + } + + return EOK; +} + +errno_t +ssh_protocol_parse_user(struct cli_ctx *cli_ctx, + const char *default_domain, + const char **_name, + const char **_domain) +{ + return ssh_protocol_parse_request(cli_ctx, default_domain, + _name, NULL, _domain); +} + +errno_t +ssh_protocol_parse_host(struct cli_ctx *cli_ctx, + const char **_name, + const char **_alias, + const char **_domain) +{ + return ssh_protocol_parse_request(cli_ctx, NULL, _name, _alias, _domain); +} diff --git a/src/responder/ssh/ssh_reply.c b/src/responder/ssh/ssh_reply.c new file mode 100644 index 0000000..edeb287 --- /dev/null +++ b/src/responder/ssh/ssh_reply.c @@ -0,0 +1,429 @@ +/* + Authors: + Jan Cholasta <jcholast@redhat.com> + + Copyright (C) 2012 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 "config.h" + +#include <talloc.h> +#include <ldb.h> + +#include "db/sysdb.h" +#include "util/util.h" +#include "util/crypto/sss_crypto.h" +#include "util/sss_ssh.h" +#include "util/cert.h" +#include "responder/common/responder.h" +#include "responder/common/responder_packet.h" +#include "responder/common/cache_req/cache_req.h" +#include "responder/ssh/ssh_private.h" + +/* Locally used flag for libldb's ldb_message_element structure to indicate + * binary data. Since the related data is only used in memory it is safe. If + * should be used with care if libldb's I/O operations are involved. */ +#define SSS_EL_FLAG_BIN_DATA (1<<4) + +static errno_t decode_and_add_base64_data(struct sss_packet *packet, + struct ldb_message_element *el, + bool skip_base64_decode, + size_t fqname_len, + const char *fqname, + size_t *c) +{ + uint8_t *key; + size_t key_len; + uint8_t *body; + size_t body_len; + int ret; + size_t d; + TALLOC_CTX *tmp_ctx; + + if (el == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "Mssing element, nothing to do.\n"); + return EOK; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + for (d = 0; d < el->num_values; d++) { + if (el->values[d].length == 0 && el->values[d].data == NULL) { + /* skip empty keys, e.g. due to invalid certificate */ + continue; + } + if (skip_base64_decode || (el->flags & SSS_EL_FLAG_BIN_DATA)) { + key = el->values[d].data; + key_len = el->values[d].length; + } else { + key = sss_base64_decode(tmp_ctx, (const char *) el->values[d].data, + &key_len); + if (key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sss_base64_decode failed.\n"); + ret = ENOMEM; + goto done; + } + } + + ret = sss_packet_grow(packet, + 3*sizeof(uint32_t) + key_len + fqname_len); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_packet_grow failed.\n"); + goto done; + } + sss_packet_get_body(packet, &body, &body_len); + + SAFEALIGN_SET_UINT32(body+(*c), 0, c); + SAFEALIGN_SET_UINT32(body+(*c), fqname_len, c); + safealign_memcpy(body+(*c), fqname, fqname_len, c); + SAFEALIGN_SET_UINT32(body+(*c), key_len, c); + safealign_memcpy(body+(*c), key, key_len, c); + + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +struct ssh_get_output_keys_state { + struct tevent_context *ev; + struct cli_ctx *cli_ctx; + struct ldb_message *msg; + char *cert_verification_opts; + int p11_child_timeout; + struct ssh_ctx *ssh_ctx; + struct ldb_message_element *user_cert; + struct ldb_message_element *user_cert_override; + struct ldb_message_element *current_cert; + + const char *name; + struct ldb_message_element **elements; + uint32_t num_keys; + size_t iter; +}; + +void ssh_get_output_keys_done(struct tevent_req *subreq); + +struct tevent_req *ssh_get_output_keys_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct cli_ctx *cli_ctx, + struct sss_domain_info *domain, + struct ldb_message *msg) +{ + struct tevent_req *req; + struct tevent_req *subreq; + errno_t ret; + struct ssh_get_output_keys_state *state; + + req = tevent_req_create(mem_ctx, &state, struct ssh_get_output_keys_state); + if (req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "tevent_req_create failed.\n"); + return NULL; + } + + state->ev = ev; + state->cli_ctx = cli_ctx; + state->msg = msg; + state->num_keys = 0; + state->iter = 0; + state->ssh_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct ssh_ctx); + if (state->ssh_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Missing ssh responder context.\n"); + ret = EINVAL; + goto done; + } + + state->name = ldb_msg_find_attr_as_string(state->msg, SYSDB_NAME, NULL); + if (state->name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Missing name.\n"); + ret = EINVAL; + goto done; + } + + state->elements = talloc_zero_array(state, struct ldb_message_element *, 6); + if (state->elements == NULL) { + ret = ENOMEM; + goto done; + } + + state->elements[state->iter] = ldb_msg_find_element(state->msg, + SYSDB_SSH_PUBKEY); + if (state->elements[state->iter] != NULL) { + state->num_keys += state->elements[state->iter]->num_values; + state->iter++; + } + + state->elements[state->iter] = ldb_msg_find_element(state->msg, + ORIGINALAD_PREFIX SYSDB_SSH_PUBKEY); + if (state->elements[state->iter] != NULL) { + state->num_keys += state->elements[state->iter]->num_values; + state->iter++; + } + + if (DOM_HAS_VIEWS(domain)) { + state->elements[state->iter] = ldb_msg_find_element(state->msg, + OVERRIDE_PREFIX SYSDB_SSH_PUBKEY); + if (state->elements[state->iter] != NULL) { + state->num_keys += state->elements[state->iter]->num_values; + state->iter++; + } + } + + if (!state->ssh_ctx->use_cert_keys) { + DEBUG(SSSDBG_TRACE_ALL, "Skipping keys from certificates.\n"); + ret = EOK; + goto done; + } + + if (state->ssh_ctx->cert_rules_error) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Skipping keys from certificates because there was an error " + "while processing matching rules.\n"); + ret = EOK; + goto done; + } + + ret = confdb_get_string(cli_ctx->rctx->cdb, state, + CONFDB_MONITOR_CONF_ENTRY, + CONFDB_MONITOR_CERT_VERIFICATION, NULL, + &state->cert_verification_opts); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read verification options from confdb: [%d] %s\n", + ret, sss_strerror(ret)); + goto done; + } + + state->p11_child_timeout = -1; + ret = confdb_get_int(cli_ctx->rctx->cdb, CONFDB_SSH_CONF_ENTRY, + CONFDB_PAM_P11_CHILD_TIMEOUT, -1, + &state->p11_child_timeout); + if (ret != EOK || state->p11_child_timeout == -1) { + /* check pam configuration as well or use default */ + ret = confdb_get_int(cli_ctx->rctx->cdb, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_P11_CHILD_TIMEOUT, + P11_CHILD_TIMEOUT_DEFAULT, + &state->p11_child_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to read p11_child_timeout from confdb: [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + } + + state->user_cert = ldb_msg_find_element(state->msg, SYSDB_USER_CERT); + if (DOM_HAS_VIEWS(domain)) { + state->user_cert_override = ldb_msg_find_element(state->msg, + OVERRIDE_PREFIX SYSDB_USER_CERT); + } + + if (state->user_cert == NULL && state->user_cert_override == NULL) { + /* no certificates to convert, we are done */ + ret = EOK; + goto done; + } + + state->current_cert = state->user_cert != NULL ? state->user_cert + : state->user_cert_override; + + subreq = cert_to_ssh_key_send(state, state->ev, + P11_CHILD_LOG_FILE, + state->p11_child_timeout, + state->ssh_ctx->ca_db, + state->ssh_ctx->sss_certmap_ctx, + state->current_cert->num_values, + state->current_cert->values, + state->cert_verification_opts); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "cert_to_ssh_key_send failed.\n"); + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, ssh_get_output_keys_done, req); + + ret = EAGAIN; + +done: + if (ret != EAGAIN) { + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + } + + return req; +} + +void ssh_get_output_keys_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ssh_get_output_keys_state *state = tevent_req_data(req, + struct ssh_get_output_keys_state); + int ret; + struct ldb_val *keys; + size_t valid_keys; + + ret = cert_to_ssh_key_recv(subreq, state, &keys, &valid_keys); + talloc_zfree(subreq); + if (ret != EOK) { + if (ret == ERR_P11_CHILD_TIMEOUT) { + DEBUG(SSSDBG_MINOR_FAILURE, + "cert_to_ssh_key request timeout, " + "consider increasing p11_child_timeout.\n"); + } else { + DEBUG(SSSDBG_MINOR_FAILURE, + "cert_to_ssh_key request failed, ssh keys derived " + "from certificates will be skipped.\n"); + } + /* Ignore ssh keys from certificates and return what we already have */ + tevent_req_done(req); + return; + } + + state->elements[state->iter] = talloc_zero(state->elements, + struct ldb_message_element); + if (state->elements[state->iter] == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n"); + ret = ENOMEM; + goto done; + } + state->elements[state->iter]->values = talloc_steal( + state->elements[state->iter], + keys); + state->elements[state->iter]->num_values = state->current_cert->num_values; + state->elements[state->iter]->flags |= SSS_EL_FLAG_BIN_DATA; + state->num_keys += valid_keys; + + if (state->current_cert == state->user_cert) { + state->current_cert = state->user_cert_override; + } else if (state->current_cert == state->user_cert_override) { + state->current_cert = NULL; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected certificate pointer.\n"); + tevent_req_error(req, EINVAL); + return; + } + + if (state->current_cert == NULL) { + /* done */ + ret = EOK; + goto done; + } + + subreq = cert_to_ssh_key_send(state, state->ev, NULL, + state->p11_child_timeout, + state->ssh_ctx->ca_db, + state->ssh_ctx->sss_certmap_ctx, + state->current_cert->num_values, + state->current_cert->values, + state->cert_verification_opts); + if (subreq == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "cert_to_ssh_key_send failed.\n"); + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, ssh_get_output_keys_done, req); + return; +done: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + + return; +} + +errno_t ssh_get_output_keys_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + struct sized_string *name, + struct ldb_message_element ***elements, + uint32_t *num_keys) +{ + struct ssh_get_output_keys_state *state = tevent_req_data(req, + struct ssh_get_output_keys_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (name != NULL) { + name->str = talloc_strdup(mem_ctx, state->name); + name->len = strlen(name->str) + 1; + } + + if (elements != NULL) { + *elements = talloc_steal(mem_ctx, state->elements); + } + + if (num_keys != NULL) { + *num_keys = state->num_keys; + } + + return EOK; +} + +errno_t +ssh_protocol_build_reply(struct sss_packet *packet, + struct sized_string name, + struct ldb_message_element **elements, + uint32_t num_keys) +{ + size_t body_len; + uint8_t *body; + size_t c = 0; + errno_t ret; + int i; + + ret = sss_packet_grow(packet, 2 * sizeof(uint32_t)); + if (ret != EOK) { + goto done; + } + + sss_packet_get_body(packet, &body, &body_len); + + SAFEALIGN_SET_UINT32(&body[c], num_keys, &c); + SAFEALIGN_SET_UINT32(&body[c], 0, &c); + + if (num_keys == 0) { + ret = EOK; + goto done; + } + + for (i = 0; elements[i] != NULL; i++) { + ret = decode_and_add_base64_data(packet, elements[i], false, + name.len, name.str, &c); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "decode_and_add_base64_data failed.\n"); + goto done; + } + } + + ret = EOK; + +done: + + return ret; +} diff --git a/src/responder/ssh/sshsrv.c b/src/responder/ssh/sshsrv.c new file mode 100644 index 0000000..91fb77b --- /dev/null +++ b/src/responder/ssh/sshsrv.c @@ -0,0 +1,235 @@ +/* + Authors: + Jan Cholasta <jcholast@redhat.com> + + Copyright (C) 2012 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 <popt.h> + +#include "util/util.h" +#include "util/child_common.h" +#include "confdb/confdb.h" +#include "responder/common/responder.h" +#include "responder/ssh/ssh_private.h" +#include "providers/data_provider.h" +#include "sss_iface/sss_iface_async.h" + +int ssh_process_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct confdb_ctx *cdb) +{ + struct resp_ctx *rctx; + struct sss_cmd_table *ssh_cmds; + struct ssh_ctx *ssh_ctx; + int ret; + + ssh_cmds = get_ssh_cmds(); + ret = sss_process_init(mem_ctx, ev, cdb, + ssh_cmds, + SSS_SSH_SOCKET_NAME, -1, NULL, -1, + CONFDB_SSH_CONF_ENTRY, + SSS_BUS_SSH, SSS_SSH_SBUS_SERVICE_NAME, + sss_connection_setup, + &rctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "sss_process_init() failed\n"); + return ret; + } + + ssh_ctx = talloc_zero(rctx, struct ssh_ctx); + if (!ssh_ctx) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error initializing ssh_ctx\n"); + ret = ENOMEM; + goto fail; + } + + ssh_ctx->rctx = rctx; + ssh_ctx->rctx->pvt_ctx = ssh_ctx; + + ret = sss_names_init_from_args(ssh_ctx, + SSS_DEFAULT_RE, + "%1$s@%2$s", &ssh_ctx->snctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error initializing regex data\n"); + goto fail; + } + + /* Get responder options */ + + /* Get ssh_hash_known_hosts option */ + ret = confdb_get_bool(ssh_ctx->rctx->cdb, + CONFDB_SSH_CONF_ENTRY, CONFDB_SSH_HASH_KNOWN_HOSTS, + CONFDB_DEFAULT_SSH_HASH_KNOWN_HOSTS, + &ssh_ctx->hash_known_hosts); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Error reading from confdb (%d) [%s]\n", + ret, strerror(ret)); + goto fail; + } + + /* Get ssh_known_hosts_timeout option */ + ret = confdb_get_int(ssh_ctx->rctx->cdb, + CONFDB_SSH_CONF_ENTRY, CONFDB_SSH_KNOWN_HOSTS_TIMEOUT, + CONFDB_DEFAULT_SSH_KNOWN_HOSTS_TIMEOUT, + &ssh_ctx->known_hosts_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Error reading from confdb (%d) [%s]\n", + ret, strerror(ret)); + goto fail; + } + + ret = confdb_get_string(ssh_ctx->rctx->cdb, ssh_ctx, + CONFDB_SSH_CONF_ENTRY, CONFDB_SSH_CA_DB, + CONFDB_DEFAULT_SSH_CA_DB, &ssh_ctx->ca_db); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Error reading CA DB from confdb (%d) [%s]\n", + ret, strerror(ret)); + goto fail; + } + + ret = confdb_get_bool(ssh_ctx->rctx->cdb, CONFDB_SSH_CONF_ENTRY, + CONFDB_SSH_USE_CERT_KEYS, + CONFDB_DEFAULT_SSH_USE_CERT_KEYS, + &ssh_ctx->use_cert_keys); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE,"Error reading option " + CONFDB_SSH_USE_CERT_KEYS + "from confdb (%d) [%s]\n", + ret, strerror(ret)); + goto fail; + } + + ret = confdb_get_string_as_list(ssh_ctx->rctx->cdb, ssh_ctx, + CONFDB_SSH_CONF_ENTRY, + CONFDB_SSH_USE_CERT_RULES, + &ssh_ctx->cert_rules); + if (ret == ENOENT) { + ssh_ctx->cert_rules = NULL; + } else if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Error reading " CONFDB_SSH_USE_CERT_RULES + " from confdb (%d) [%s].\n", ret, + sss_strerror(ret)); + goto fail; + } + + ret = schedule_get_domains_task(rctx, rctx->ev, rctx, NULL, NULL, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "schedule_get_domains_tasks failed.\n"); + goto fail; + } + + /* The responder is initialized. Now tell it to the monitor. */ + ret = sss_monitor_service_init(rctx, rctx->ev, SSS_BUS_SSH, + SSS_SSH_SBUS_SERVICE_NAME, + SSS_SSH_SBUS_SERVICE_VERSION, + MT_SVC_SERVICE, + &rctx->last_request_time, &rctx->mon_conn); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error setting up message bus\n"); + goto fail; + } + + ret = sss_resp_register_service_iface(rctx); + if (ret != EOK) { + goto fail; + } + + DEBUG(SSSDBG_TRACE_FUNC, "SSH Initialization complete\n"); + + return EOK; + +fail: + talloc_free(rctx); + return ret; +} + +int main(int argc, const char *argv[]) +{ + int opt; + poptContext pc; + char *opt_logger = NULL; + struct main_context *main_ctx; + int ret; + uid_t uid = 0; + gid_t gid = 0; + + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_MAIN_OPTS + SSSD_LOGGER_OPTS + SSSD_SERVER_OPTS(uid, gid) + SSSD_RESPONDER_OPTS + POPT_TABLEEND + }; + + /* Set debug level to invalid value so we can decide if -d 0 was used. */ + debug_level = SSSDBG_INVALID; + + umask(DFL_RSP_UMASK); + + pc = poptGetContext(argv[0], argc, argv, long_options, 0); + while((opt = poptGetNextOpt(pc)) != -1) { + switch(opt) { + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + return 1; + } + } + + poptFreeContext(pc); + + /* set up things like debug, signals, daemonization, etc. */ + debug_log_file = "sssd_ssh"; + DEBUG_INIT(debug_level, opt_logger); + + /* server_setup() might switch to an unprivileged user, so the permissions + * for p11_child.log have to be fixed first. We might call p11_child to + * validate certificates. */ + ret = chown_debug_file("p11_child", uid, gid); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Cannot chown the p11_child debug file, " + "debugging might not work!\n"); + } + + ret = server_setup("ssh", true, 0, uid, gid, + CONFDB_SSH_CONF_ENTRY, &main_ctx, true); + if (ret != EOK) { + return 2; + } + + ret = die_if_parent_died(); + if (ret != EOK) { + /* This is not fatal, don't return */ + DEBUG(SSSDBG_OP_FAILURE, "Could not set up to exit " + "when parent process does\n"); + } + + ret = ssh_process_init(main_ctx, + main_ctx->event_ctx, + main_ctx->confdb_ctx); + if (ret != EOK) { + return 3; + } + + /* loop on main */ + server_loop(main_ctx); + + return 0; +} diff --git a/src/responder/sudo/sudosrv.c b/src/responder/sudo/sudosrv.c new file mode 100644 index 0000000..8568e6e --- /dev/null +++ b/src/responder/sudo/sudosrv.c @@ -0,0 +1,223 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2011 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 <popt.h> + +#include "util/util.h" +#include "confdb/confdb.h" +#include "responder/common/responder.h" +#include "responder/sudo/sudosrv_private.h" +#include "providers/data_provider.h" +#include "responder/common/negcache.h" +#include "sss_iface/sss_iface_async.h" + +int sudo_process_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct confdb_ctx *cdb, + int pipe_fd) +{ + struct resp_ctx *rctx; + struct sss_cmd_table *sudo_cmds; + struct sudo_ctx *sudo_ctx; + int ret; + + sudo_cmds = get_sudo_cmds(); + ret = sss_process_init(mem_ctx, ev, cdb, + sudo_cmds, + SSS_SUDO_SOCKET_NAME, pipe_fd, /* custom permissions on socket */ + NULL, -1, /* No private socket */ + CONFDB_SUDO_CONF_ENTRY, + SSS_BUS_SUDO, SSS_SUDO_SBUS_SERVICE_NAME, + sss_connection_setup, + &rctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "sss_process_init() failed\n"); + return ret; + } + + sudo_ctx = talloc_zero(rctx, struct sudo_ctx); + if (!sudo_ctx) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error initializing sudo_ctx\n"); + ret = ENOMEM; + goto fail; + } + + sudo_ctx->rctx = rctx; + sudo_ctx->rctx->pvt_ctx = sudo_ctx; + + sss_ncache_prepopulate(sudo_ctx->rctx->ncache, sudo_ctx->rctx->cdb, rctx); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "failed to set ncache for sudo's filter_users\n"); + goto fail; + } + + /* Get sudo_timed option */ + ret = confdb_get_bool(sudo_ctx->rctx->cdb, + CONFDB_SUDO_CONF_ENTRY, CONFDB_SUDO_TIMED, + CONFDB_DEFAULT_SUDO_TIMED, + &sudo_ctx->timed); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Error reading from confdb (%d) [%s]\n", + ret, strerror(ret)); + goto fail; + } + + /* Get sudo_inverse_order option */ + ret = confdb_get_bool(sudo_ctx->rctx->cdb, + CONFDB_SUDO_CONF_ENTRY, CONFDB_SUDO_INVERSE_ORDER, + CONFDB_DEFAULT_SUDO_INVERSE_ORDER, + &sudo_ctx->inverse_order); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Error reading from confdb (%d) [%s]\n", + ret, strerror(ret)); + goto fail; + } + + /* Get sudo_inverse_order option */ + ret = confdb_get_int(sudo_ctx->rctx->cdb, + CONFDB_SUDO_CONF_ENTRY, CONFDB_SUDO_THRESHOLD, + CONFDB_DEFAULT_SUDO_THRESHOLD, + &sudo_ctx->threshold); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Error reading from confdb (%d) [%s]\n", + ret, strerror(ret)); + goto fail; + } + + ret = schedule_get_domains_task(rctx, rctx->ev, rctx, NULL, NULL, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "schedule_get_domains_tasks failed.\n"); + goto fail; + } + + /* The responder is initialized. Now tell it to the monitor. */ + ret = sss_monitor_service_init(rctx, rctx->ev, SSS_BUS_SUDO, + SSS_SUDO_SBUS_SERVICE_NAME, + SSS_SUDO_SBUS_SERVICE_VERSION, + MT_SVC_SERVICE, + &rctx->last_request_time, &rctx->mon_conn); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "fatal error setting up message bus\n"); + goto fail; + } + + ret = sss_resp_register_service_iface(rctx); + if (ret != EOK) { + goto fail; + } + + DEBUG(SSSDBG_TRACE_FUNC, "SUDO Initialization complete\n"); + + return EOK; + +fail: + talloc_free(rctx); + return ret; +} + +int main(int argc, const char *argv[]) +{ + int opt; + poptContext pc; + char *opt_logger = NULL; + struct main_context *main_ctx; + int ret; + int pipe_fd = -1; + uid_t uid = 0; + gid_t gid = 0; + + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_MAIN_OPTS + SSSD_LOGGER_OPTS + SSSD_SERVER_OPTS(uid, gid) + SSSD_RESPONDER_OPTS + POPT_TABLEEND + }; + + /* Set debug level to invalid value so we can decide if -d 0 was used. */ + debug_level = SSSDBG_INVALID; + + umask(DFL_RSP_UMASK); + + pc = poptGetContext(argv[0], argc, argv, long_options, 0); + while((opt = poptGetNextOpt(pc)) != -1) { + switch(opt) { + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + return 1; + } + } + + poptFreeContext(pc); + + /* set up things like debug, signals, daemonization, etc. */ + debug_log_file = "sssd_sudo"; + DEBUG_INIT(debug_level, opt_logger); + + if (!is_socket_activated()) { + /* Create pipe file descriptors here with right ownerschip */ + ret = create_pipe_fd(SSS_SUDO_SOCKET_NAME, &pipe_fd, SSS_DFL_UMASK); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "create_pipe_fd failed [%d]: %s.\n", + ret, sss_strerror(ret)); + return 4; + } + + ret = chown(SSS_SUDO_SOCKET_NAME, uid, 0); + if (ret != 0) { + ret = errno; + close(pipe_fd); + DEBUG(SSSDBG_FATAL_FAILURE, + "create_pipe_fd failed [%d]: %s.\n", + ret, sss_strerror(ret)); + return 5; + } + } + + ret = server_setup("sudo", true, 0, uid, gid, CONFDB_SUDO_CONF_ENTRY, + &main_ctx, true); + if (ret != EOK) { + return 2; + } + + ret = die_if_parent_died(); + if (ret != EOK) { + /* This is not fatal, don't return */ + DEBUG(SSSDBG_OP_FAILURE, "Could not set up to exit " + "when parent process does\n"); + } + + ret = sudo_process_init(main_ctx, + main_ctx->event_ctx, + main_ctx->confdb_ctx, pipe_fd); + if (ret != EOK) { + return 3; + } + + /* loop on main */ + server_loop(main_ctx); + + return 0; +} diff --git a/src/responder/sudo/sudosrv_cmd.c b/src/responder/sudo/sudosrv_cmd.c new file mode 100644 index 0000000..63b548f --- /dev/null +++ b/src/responder/sudo/sudosrv_cmd.c @@ -0,0 +1,303 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2011 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 <stdint.h> +#include <errno.h> +#include <talloc.h> + +#include "util/util.h" +#include "responder/common/responder.h" +#include "responder/common/responder_packet.h" +#include "responder/sudo/sudosrv_private.h" +#include "db/sysdb_sudo.h" +#include "sss_client/sss_cli.h" +#include "responder/common/negcache.h" + +static errno_t sudosrv_cmd_send_reply(struct sudo_cmd_ctx *cmd_ctx, + uint8_t *response_body, + size_t response_len) +{ + errno_t ret; + uint8_t *packet_body = NULL; + size_t packet_len = 0; + struct cli_ctx *cli_ctx = cmd_ctx->cli_ctx; + struct cli_protocol *pctx; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol); + + ret = sss_packet_new(pctx->creq, 0, + sss_packet_get_cmd(pctx->creq->in), + &pctx->creq->out); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to create a new packet [%d]; %s\n", + ret, strerror(ret)); + goto done; + } + + ret = sss_packet_grow(pctx->creq->out, response_len); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to create response: %s\n", strerror(ret)); + goto done; + } + sss_packet_get_body(pctx->creq->out, &packet_body, &packet_len); + memcpy(packet_body, response_body, response_len); + + sss_packet_set_error(pctx->creq->out, EOK); + sss_cmd_done(cmd_ctx->cli_ctx, cmd_ctx); + + ret = EOK; + +done: + talloc_zfree(tmp_ctx); + return ret; +} + +static errno_t sudosrv_cmd_send_error(TALLOC_CTX *mem_ctx, + struct sudo_cmd_ctx *cmd_ctx, + uint32_t error) +{ + uint8_t *response_body = NULL; + size_t response_len = 0; + int ret = EOK; + + if (error == EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Everything is fine but we are " + "returning error?\n"); + return EFAULT; + } + + ret = sudosrv_build_response(mem_ctx, error, 0, NULL, + &response_body, &response_len); + if (ret != EOK) { + return ret; + } + + return sudosrv_cmd_send_reply(cmd_ctx, response_body, response_len); +} + +errno_t sudosrv_cmd_reply(struct sudo_cmd_ctx *cmd_ctx, int ret) +{ + uint8_t *response_body = NULL; + size_t response_len = 0; + uint32_t num_rules = cmd_ctx->num_rules; + struct sysdb_attrs **rules = cmd_ctx->rules; + + switch (ret) { + case EOK: + /* + * Parent of cmd_ctx->rules is in-memory cache, we must not talloc_free it! + */ + if (cmd_ctx->sudo_ctx->timed) { + /* filter rules by time */ + + DEBUG(SSSDBG_TRACE_FUNC, "Applying time restrictions on" + "%u rules\n", cmd_ctx->num_rules); + + ret = sysdb_sudo_filter_rules_by_time(cmd_ctx, cmd_ctx->num_rules, + cmd_ctx->rules, 0, + &num_rules, &rules); + if (ret != EOK) { + return EFAULT; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Got %u rules after time filter\n", + num_rules); + } + + /* send result */ + ret = sudosrv_build_response(cmd_ctx, SSS_SUDO_ERROR_OK, + num_rules, rules, + &response_body, &response_len); + if (ret != EOK) { + return EFAULT; + } + + ret = sudosrv_cmd_send_reply(cmd_ctx, response_body, response_len); + break; + + case EAGAIN: + /* async processing, just return here */ + return EOK; + + case EFAULT: + /* very bad error */ + return EFAULT; + + + /* case ENOENT: + * - means user not found + * - send error ENOENT + */ + + default: + /* send error */ + ret = sudosrv_cmd_send_error(cmd_ctx, cmd_ctx, ret); + break; + } + + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Fatal error, killing connection!\n"); + talloc_free(cmd_ctx->cli_ctx); + return EFAULT; + } + + return EOK; +} + +static void sudosrv_cmd_done(struct tevent_req *req); + +static int sudosrv_cmd(enum sss_sudo_type type, struct cli_ctx *cli_ctx) +{ + struct tevent_req *req = NULL; + struct sudo_cmd_ctx *cmd_ctx = NULL; + uint8_t *query_body = NULL; + size_t query_len = 0; + struct cli_protocol *pctx; + uint32_t protocol; + errno_t ret; + + /* create cmd_ctx */ + + cmd_ctx = talloc_zero(cli_ctx, struct sudo_cmd_ctx); + if (cmd_ctx == NULL) { + /* kill the connection here as we have no context for reply */ + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory?\n"); + return ENOMEM; + } + + cmd_ctx->cli_ctx = cli_ctx; + cmd_ctx->type = type; + cmd_ctx->sudo_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct sudo_ctx); + if (cmd_ctx->sudo_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "sudo_ctx not set, killing connection!\n"); + return EFAULT; + } + + pctx = talloc_get_type(cli_ctx->protocol_ctx, struct cli_protocol); + protocol = pctx->cli_protocol_version->version; + + /* if protocol is invalid return */ + switch (protocol) { + case 0: + DEBUG(SSSDBG_FATAL_FAILURE, "Protocol [%d] is not secure. " + "SSSD does not allow to use this protocol.\n", protocol); + ret = EFAULT; + goto done; + break; + case SSS_SUDO_PROTOCOL_VERSION: + DEBUG(SSSDBG_TRACE_INTERNAL, "Using protocol version [%d]\n", + protocol); + break; + default: + DEBUG(SSSDBG_FATAL_FAILURE, "Invalid protocol version [%d]!\n", + protocol); + ret = EFAULT; + goto done; + } + + /* parse query */ + sss_packet_get_body(pctx->creq->in, &query_body, &query_len); + if (query_len <= 0 || query_body == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Query is empty\n"); + ret = EINVAL; + goto done; + } + + ret = sudosrv_parse_query(cmd_ctx, query_body, query_len, + &cmd_ctx->rawname, &cmd_ctx->uid); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse sudo query [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + req = sudosrv_get_rules_send(cmd_ctx, cli_ctx->ev, cmd_ctx->sudo_ctx, + cmd_ctx->type, cmd_ctx->uid, + cmd_ctx->rawname); + if (req == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(req, sudosrv_cmd_done, cmd_ctx); + + ret = EAGAIN; + +done: + return sudosrv_cmd_reply(cmd_ctx, ret); +} + +static void sudosrv_cmd_done(struct tevent_req *req) +{ + struct sudo_cmd_ctx *cmd_ctx; + errno_t ret; + + cmd_ctx = tevent_req_callback_data(req, struct sudo_cmd_ctx); + + ret = sudosrv_get_rules_recv(cmd_ctx, req, &cmd_ctx->rules, + &cmd_ctx->num_rules); + talloc_zfree(req); + if (ret != EOK) { + DEBUG((ret == ENOENT) ? SSSDBG_MINOR_FAILURE : SSSDBG_OP_FAILURE, + "Unable to obtain cached rules [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + +done: + sudosrv_cmd_reply(cmd_ctx, ret); +} + +static int sudosrv_cmd_get_sudorules(struct cli_ctx *cli_ctx) +{ + return sudosrv_cmd(SSS_SUDO_USER, cli_ctx); +} + +static int sudosrv_cmd_get_defaults(struct cli_ctx *cli_ctx) +{ + return sudosrv_cmd(SSS_SUDO_DEFAULTS, cli_ctx); +} + +struct cli_protocol_version *register_cli_protocol_version(void) +{ + static struct cli_protocol_version sudo_cli_protocol_version[] = { + {1, "2012-05-14", "require uid and domain"}, + {0, NULL, NULL} + }; + + return sudo_cli_protocol_version; +} + +struct sss_cmd_table *get_sudo_cmds(void) { + static struct sss_cmd_table sudo_cmds[] = { + {SSS_GET_VERSION, sss_cmd_get_version}, + {SSS_SUDO_GET_SUDORULES, sudosrv_cmd_get_sudorules}, + {SSS_SUDO_GET_DEFAULTS, sudosrv_cmd_get_defaults}, + {SSS_CLI_NULL, NULL} + }; + + return sudo_cmds; +} diff --git a/src/responder/sudo/sudosrv_dp.c b/src/responder/sudo/sudosrv_dp.c new file mode 100644 index 0000000..465c0f2 --- /dev/null +++ b/src/responder/sudo/sudosrv_dp.c @@ -0,0 +1,265 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + Jakub Hrozek <jhrozek@redhat.com> + + Copyright (C) 2011 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 <talloc.h> +#include <tevent.h> +#include <dbus/dbus.h> + +#include "util/util.h" +#include "providers/data_provider.h" +#include "providers/data_provider_req.h" +#include "responder/common/responder.h" +#include "responder/sudo/sudosrv_private.h" +#include "db/sysdb.h" +#include "sss_iface/sss_iface_async.h" + +static DBusMessage * +sss_dp_get_sudoers_msg(TALLOC_CTX *mem_ctx, + const char *bus_name, + struct sss_domain_info *dom, + bool fast_reply, + enum sss_dp_sudo_type type, + const char *name, + uint32_t num_rules, + struct sysdb_attrs **rules) +{ + DBusMessage *msg; + DBusMessageIter iter; + DBusMessageIter array_iter; + dbus_bool_t dbret; + errno_t ret; + uint32_t be_type = 0; + uint32_t dp_flags = 0; + const char *rule_name = NULL; + uint32_t i; + + switch (type) { + case SSS_DP_SUDO_REFRESH_RULES: + be_type = BE_REQ_SUDO_RULES; + break; + case SSS_DP_SUDO_FULL_REFRESH: + be_type = BE_REQ_SUDO_FULL; + break; + } + + if (fast_reply) { + dp_flags |= DP_FAST_REPLY; + } + + msg = dbus_message_new_method_call(bus_name, + SSS_BUS_PATH, + "sssd.dataprovider", + "sudoHandler"); + if (msg == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory?!\n"); + return NULL; + } + + /* create the message */ + DEBUG(SSSDBG_TRACE_FUNC, + "Creating SUDOers request for [%s][%u][%s][%u]\n", + dom->name, be_type, name, num_rules); + + dbus_message_iter_init_append(msg, &iter); + + dbret = dbus_message_iter_append_basic(&iter, DBUS_TYPE_UINT32, &dp_flags); + if (dbret == FALSE) { + goto fail; + } + + /* BE TYPE */ + dbret = dbus_message_iter_append_basic(&iter, DBUS_TYPE_UINT32, &be_type); + if (dbret == FALSE) { + goto fail; + } + + /* BE TYPE SPECIFIC */ + if (be_type & BE_REQ_SUDO_RULES) { + dbret = dbus_message_iter_append_basic(&iter, DBUS_TYPE_UINT32, + &num_rules); + if (dbret == FALSE) { + goto fail; + } + + dbret = dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING_AS_STRING, + &array_iter); + if (dbret == FALSE) { + goto fail; + } + + for (i = 0; i < num_rules; i++) { + ret = sysdb_attrs_get_string(rules[i], SYSDB_NAME, &rule_name); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not get rule name [%d]: %s\n", + ret, strerror(ret)); + goto fail; + } + + dbret = dbus_message_iter_append_basic(&array_iter, + DBUS_TYPE_STRING, + &rule_name); + if (dbret == FALSE) { + goto fail; + } + } + + dbret = dbus_message_iter_close_container(&iter, &array_iter); + if (dbret == FALSE) { + goto fail; + } + } + + return msg; + +fail: + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to build message\n"); + dbus_message_unref(msg); + return NULL; +} + +struct sss_dp_get_sudoers_state { + uint16_t dp_error; + uint32_t error; + const char *error_message; +}; + +static void sss_dp_get_sudoers_done(struct tevent_req *subreq); + +struct tevent_req * +sss_dp_get_sudoers_send(TALLOC_CTX *mem_ctx, + struct resp_ctx *rctx, + struct sss_domain_info *dom, + bool fast_reply, + enum sss_dp_sudo_type type, + const char *name, + uint32_t num_rules, + struct sysdb_attrs **rules) +{ + struct sss_dp_get_sudoers_state *state; + struct tevent_req *subreq; + struct tevent_req *req; + struct be_conn *be_conn; + DBusMessage *msg; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct sss_dp_get_sudoers_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create tevent request!\n"); + return NULL; + } + + if (is_files_provider(dom)) { + DEBUG(SSSDBG_TRACE_INTERNAL, "Domain %s does not check DP\n", + dom->name); + state->dp_error = DP_ERR_OK; + state->error = EOK; + state->error_message = talloc_strdup(state, "Success"); + if (state->error_message == NULL) { + ret = ENOMEM; + goto done; + } + ret = EOK; + goto done; + } + + ret = sss_dp_get_domain_conn(rctx, dom->conn_name, &be_conn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "BUG: The Data Provider connection for %s is not available!\n", + dom->name); + ret = EIO; + goto done; + } + + msg = sss_dp_get_sudoers_msg(state, be_conn->bus_name, dom, fast_reply, + type, name, num_rules, rules); + if (msg == NULL) { + ret = ENOMEM; + goto done; + } + + subreq = sbus_call_dp_dp_sudoHandler_send(state, be_conn->conn, msg); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create subrequest!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sss_dp_get_sudoers_done, req); + + ret = EAGAIN; + +done: +#ifdef BUILD_FILES_PROVIDER + if (ret == EOK) { + tevent_req_done(req); + tevent_req_post(req, rctx->ev); + } else +#endif + if (ret != EAGAIN) { + tevent_req_error(req, ret); + tevent_req_post(req, rctx->ev); + } + + return req; +} + +static void sss_dp_get_sudoers_done(struct tevent_req *subreq) +{ + struct sss_dp_get_sudoers_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sss_dp_get_sudoers_state); + + ret = sbus_call_dp_dp_sudoHandler_recv(state, subreq, &state->dp_error, + &state->error, + &state->error_message); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); + return; +} + +errno_t +sss_dp_get_sudoers_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + uint16_t *_dp_error, + uint32_t *_error, + const char ** _error_message) +{ + struct sss_dp_get_sudoers_state *state; + state = tevent_req_data(req, struct sss_dp_get_sudoers_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_dp_error = state->dp_error; + *_error = state->error; + *_error_message = talloc_steal(mem_ctx, state->error_message); + + return EOK; +} diff --git a/src/responder/sudo/sudosrv_get_sudorules.c b/src/responder/sudo/sudosrv_get_sudorules.c new file mode 100644 index 0000000..1bcfc37 --- /dev/null +++ b/src/responder/sudo/sudosrv_get_sudorules.c @@ -0,0 +1,887 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + Jakub Hrozek <jhrozek@redhat.com> + + Copyright (C) 2011 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 "config.h" + +#include <stdint.h> +#include <string.h> +#include <talloc.h> +#include <tevent.h> + +#include "util/util.h" +#include "db/sysdb_sudo.h" +#include "responder/common/cache_req/cache_req.h" +#include "responder/sudo/sudosrv_private.h" +#include "providers/data_provider.h" + +static int +sudo_order_cmp(const void *a, const void *b, bool lower_wins) +{ + struct sysdb_attrs *r1, *r2; + uint32_t o1, o2; + int ret; + + r1 = * (struct sysdb_attrs * const *) a; + r2 = * (struct sysdb_attrs * const *) b; + if (!r1 || !r2) { + DEBUG(SSSDBG_CRIT_FAILURE, "BUG: Wrong data?\n"); + return 0; + } + + ret = sysdb_attrs_get_uint32_t(r1, SYSDB_SUDO_CACHE_AT_ORDER, &o1); + if (ret == ENOENT) { + /* man sudoers-ldap: If the sudoOrder attribute is not present, + * a value of 0 is assumed */ + o1 = 0; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot get sudoOrder value\n"); + return 0; + } + + ret = sysdb_attrs_get_uint32_t(r2, SYSDB_SUDO_CACHE_AT_ORDER, &o2); + if (ret == ENOENT) { + /* man sudoers-ldap: If the sudoOrder attribute is not present, + * a value of 0 is assumed */ + o2 = 0; + } else if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot get sudoOrder value\n"); + return 0; + } + + if (lower_wins) { + /* The lowest value takes priority. Original wrong SSSD behaviour. */ + if (o1 > o2) { + return 1; + } else if (o1 < o2) { + return -1; + } + } else { + /* The higher value takes priority. Standard LDAP behaviour. */ + if (o1 < o2) { + return 1; + } else if (o1 > o2) { + return -1; + } + } + + return 0; +} + +static int +sudo_order_low_cmp_fn(const void *a, const void *b) +{ + return sudo_order_cmp(a, b, true); +} + +static int +sudo_order_high_cmp_fn(const void *a, const void *b) +{ + return sudo_order_cmp(a, b, false); +} + +static errno_t +sort_sudo_rules(struct sysdb_attrs **rules, size_t count, bool lower_wins) +{ + if (lower_wins) { + DEBUG(SSSDBG_TRACE_FUNC, "Sorting rules with lower-wins logic\n"); + qsort(rules, count, sizeof(struct sysdb_attrs *), + sudo_order_low_cmp_fn); + } else { + DEBUG(SSSDBG_TRACE_FUNC, "Sorting rules with higher-wins logic\n"); + qsort(rules, count, sizeof(struct sysdb_attrs *), + sudo_order_high_cmp_fn); + } + + return EOK; +} + +static errno_t sudosrv_format_runas(struct resp_ctx *rctx, + struct sysdb_attrs *rule, + const char *attr) +{ + TALLOC_CTX *tmp_ctx; + struct ldb_message_element *el; + struct sss_domain_info *dom; + const char *value; + char *fqname; + unsigned int i; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + ret = sysdb_attrs_get_el_ext(rule, attr, false, &el); + if (ret == ENOENT) { + ret = EOK; + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get %s attribute " + "[%d]: %s\n", attr, ret, sss_strerror(ret)); + goto done; + } + + for (i = 0; i < el->num_values; i++) { + value = (const char *)el->values[i].data; + if (value == NULL) { + continue; + } + + dom = find_domain_by_object_name_ex(rctx->domains, value, true, + SSS_GND_DESCEND); + if (dom == NULL) { + continue; + } + + ret = sss_output_fqname(tmp_ctx, dom, value, + rctx->override_space, &fqname); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to convert %s to output fqname " + "[%d]: %s\n", value, ret, sss_strerror(ret)); + goto done; + } + + talloc_free(el->values[i].data); + el->values[i].data = (uint8_t*)talloc_steal(el->values, fqname); + el->values[i].length = strlen(fqname); + } + +done: + talloc_free(tmp_ctx); + + return ret; +} + +static errno_t sudosrv_format_rules(struct resp_ctx *rctx, + struct sysdb_attrs **rules, + uint32_t num_rules) +{ + uint32_t i; + errno_t ret = EOK; + + + for (i = 0; i < num_rules; i++) { + ret = sudosrv_format_runas(rctx, rules[i], + SYSDB_SUDO_CACHE_AT_RUNAS); + if (ret != EOK) { + return ret; + } + + ret = sudosrv_format_runas(rctx, rules[i], + SYSDB_SUDO_CACHE_AT_RUNASUSER); + if (ret != EOK) { + return ret; + } + + ret = sudosrv_format_runas(rctx, rules[i], + SYSDB_SUDO_CACHE_AT_RUNASGROUP); + if (ret != EOK) { + return ret; + } + } + + return ret; +} + +static errno_t sudosrv_query_cache(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char **attrs, + const char *filter, + struct sysdb_attrs ***_rules, + uint32_t *_count) +{ + TALLOC_CTX *tmp_ctx; + errno_t ret; + size_t count; + struct sysdb_attrs **rules; + struct ldb_message **msgs; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + DEBUG(SSSDBG_FUNC_DATA, "Searching sysdb with [%s]\n", filter); + + if (IS_SUBDOMAIN(domain)) { + /* rules are stored inside parent domain tree */ + domain = domain->parent; + } + + ret = sysdb_search_custom(tmp_ctx, domain, filter, SUDORULE_SUBDIR, + attrs, &count, &msgs); + if (ret == ENOENT) { + *_rules = NULL; + *_count = 0; + ret = EOK; + goto done; + } else if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Error looking up SUDO rules\n"); + goto done; + } + + ret = sysdb_msg2attrs(tmp_ctx, count, msgs, &rules); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Could not convert ldb message to sysdb_attrs\n"); + goto done; + } + + *_rules = talloc_steal(mem_ctx, rules); + *_count = (uint32_t)count; + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t sudosrv_expired_rules(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + uid_t uid, + const char *username, + char **groups, + struct sysdb_attrs ***_rules, + uint32_t *_num_rules) +{ + const char *attrs[] = { SYSDB_NAME, NULL }; + char *filter; + errno_t ret; + + filter = sysdb_sudo_filter_expired(NULL, username, groups, uid); + if (filter == NULL) { + return ENOMEM; + } + + ret = sudosrv_query_cache(mem_ctx, domain, attrs, filter, + _rules, _num_rules); + talloc_free(filter); + + return ret; +} + +static errno_t sudosrv_cached_rules_by_user(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + uid_t cli_uid, + uid_t orig_uid, + const char *username, + char **groupnames, + struct sysdb_attrs ***_rules, + uint32_t *_num_rules) +{ + TALLOC_CTX *tmp_ctx; + struct sysdb_attrs **rules; + uint32_t num_rules; + uint32_t i; + const char *filter; + const char *val; + errno_t ret; + const char *attrs[] = { SYSDB_OBJECTCLASS, + SYSDB_SUDO_CACHE_AT_CN, + SYSDB_SUDO_CACHE_AT_HOST, + SYSDB_SUDO_CACHE_AT_COMMAND, + SYSDB_SUDO_CACHE_AT_OPTION, + SYSDB_SUDO_CACHE_AT_RUNAS, + SYSDB_SUDO_CACHE_AT_RUNASUSER, + SYSDB_SUDO_CACHE_AT_RUNASGROUP, + SYSDB_SUDO_CACHE_AT_NOTBEFORE, + SYSDB_SUDO_CACHE_AT_NOTAFTER, + SYSDB_SUDO_CACHE_AT_ORDER, + NULL }; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + filter = sysdb_sudo_filter_user(tmp_ctx, username, groupnames, orig_uid); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sudosrv_query_cache(tmp_ctx, domain, attrs, filter, + &rules, &num_rules); + if (ret != EOK) { + goto done; + } + + val = talloc_asprintf(tmp_ctx, "#%"SPRIuid, cli_uid); + if (val == NULL) { + ret = ENOMEM; + goto done; + } + + /* Add sudoUser: #uid to prevent conflicts with fqnames. */ + DEBUG(SSSDBG_TRACE_FUNC, "Replacing sudoUser attribute with " + "sudoUser: %s\n", val); + for (i = 0; i < num_rules; i++) { + ret = sysdb_attrs_add_string(rules[i], SYSDB_SUDO_CACHE_AT_USER, val); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to alter sudoUser attribute " + "[%d]: %s\n", ret, sss_strerror(ret)); + } + } + + *_rules = talloc_steal(mem_ctx, rules); + *_num_rules = num_rules; + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t sudosrv_cached_rules_by_ng(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + uid_t uid, + const char *username, + char **groupnames, + struct sysdb_attrs ***_rules, + uint32_t *_num_rules) +{ + char *filter; + errno_t ret; + const char *attrs[] = { SYSDB_OBJECTCLASS, + SYSDB_SUDO_CACHE_AT_CN, + SYSDB_SUDO_CACHE_AT_USER, + SYSDB_SUDO_CACHE_AT_HOST, + SYSDB_SUDO_CACHE_AT_COMMAND, + SYSDB_SUDO_CACHE_AT_OPTION, + SYSDB_SUDO_CACHE_AT_RUNAS, + SYSDB_SUDO_CACHE_AT_RUNASUSER, + SYSDB_SUDO_CACHE_AT_RUNASGROUP, + SYSDB_SUDO_CACHE_AT_NOTBEFORE, + SYSDB_SUDO_CACHE_AT_NOTAFTER, + SYSDB_SUDO_CACHE_AT_ORDER, + NULL }; + + filter = sysdb_sudo_filter_netgroups(NULL, username, groupnames, uid); + if (filter == NULL) { + return ENOMEM; + } + + ret = sudosrv_query_cache(mem_ctx, domain, attrs, filter, + _rules, _num_rules); + talloc_free(filter); + + return ret; +} + +static errno_t sudosrv_cached_rules(TALLOC_CTX *mem_ctx, + struct resp_ctx *rctx, + struct sss_domain_info *domain, + uid_t cli_uid, + uid_t orig_uid, + const char *username, + char **groups, + bool inverse_order, + struct sysdb_attrs ***_rules, + uint32_t *_num_rules) +{ + TALLOC_CTX *tmp_ctx; + struct sysdb_attrs **user_rules; + struct sysdb_attrs **ng_rules; + struct sysdb_attrs **rules; + uint32_t num_user_rules; + uint32_t num_ng_rules; + uint32_t num_rules; + uint32_t rule_iter, i; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + ret = sudosrv_cached_rules_by_user(tmp_ctx, domain, + cli_uid, orig_uid, username, groups, + &user_rules, &num_user_rules); + if (ret != EOK) { + goto done; + } + + ret = sudosrv_cached_rules_by_ng(tmp_ctx, domain, + orig_uid, username, groups, + &ng_rules, &num_ng_rules); + if (ret != EOK) { + goto done; + } + + num_rules = num_user_rules + num_ng_rules; + if (num_rules == 0) { + *_rules = NULL; + *_num_rules = 0; + ret = EOK; + goto done; + } + + rules = talloc_array(tmp_ctx, struct sysdb_attrs *, num_rules); + if (rules == NULL) { + ret = ENOMEM; + goto done; + } + + rule_iter = 0; + for (i = 0; i < num_user_rules; rule_iter++, i++) { + rules[rule_iter] = talloc_steal(rules, user_rules[i]); + } + + for (i = 0; i < num_ng_rules; rule_iter++, i++) { + rules[rule_iter] = talloc_steal(rules, ng_rules[i]); + } + + ret = sort_sudo_rules(rules, num_rules, inverse_order); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not sort rules by sudoOrder\n"); + goto done; + } + + ret = sudosrv_format_rules(rctx, rules, num_rules); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not format sudo rules\n"); + goto done; + } + + *_rules = talloc_steal(mem_ctx, rules); + *_num_rules = num_rules; + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t sudosrv_cached_defaults(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + struct sysdb_attrs ***_rules, + uint32_t *_num_rules) +{ + char *filter; + errno_t ret; + const char *attrs[] = { SYSDB_OBJECTCLASS, + SYSDB_SUDO_CACHE_AT_CN, + SYSDB_SUDO_CACHE_AT_USER, + SYSDB_SUDO_CACHE_AT_HOST, + SYSDB_SUDO_CACHE_AT_COMMAND, + SYSDB_SUDO_CACHE_AT_OPTION, + SYSDB_SUDO_CACHE_AT_RUNAS, + SYSDB_SUDO_CACHE_AT_RUNASUSER, + SYSDB_SUDO_CACHE_AT_RUNASGROUP, + SYSDB_SUDO_CACHE_AT_NOTBEFORE, + SYSDB_SUDO_CACHE_AT_NOTAFTER, + SYSDB_SUDO_CACHE_AT_ORDER, + NULL }; + + filter = sysdb_sudo_filter_defaults(NULL); + if (filter == NULL) { + return ENOMEM; + } + + ret = sudosrv_query_cache(mem_ctx, domain, attrs, filter, + _rules, _num_rules); + talloc_free(filter); + + return ret; +} + +static errno_t sudosrv_fetch_rules(TALLOC_CTX *mem_ctx, + struct resp_ctx *rctx, + enum sss_sudo_type type, + struct sss_domain_info *domain, + uid_t cli_uid, + uid_t orig_uid, + const char *username, + char **groups, + bool inverse_order, + struct sysdb_attrs ***_rules, + uint32_t *_num_rules) +{ + struct sysdb_attrs **rules = NULL; + const char *debug_name = "unknown"; + uint32_t num_rules; + errno_t ret; + + switch (type) { + case SSS_SUDO_USER: + DEBUG(SSSDBG_TRACE_FUNC, "Retrieving rules for [%s@%s]\n", + username, domain->name); + debug_name = "rules"; + + ret = sudosrv_cached_rules(mem_ctx, rctx, domain, + cli_uid, orig_uid, username, groups, + inverse_order, &rules, &num_rules); + + break; + case SSS_SUDO_DEFAULTS: + debug_name = "default options"; + DEBUG(SSSDBG_TRACE_FUNC, "Retrieving default options for [%s@%s]\n", + username, domain->name); + + ret = sudosrv_cached_defaults(mem_ctx, domain, &rules, &num_rules); + + break; + default: + ret = EINVAL; + } + + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to retrieve %s [%d]: %s\n", + debug_name, ret, sss_strerror(ret)); + return ret; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Returning %u %s for [%s@%s]\n", + num_rules, debug_name, username, domain->name); + + *_rules = rules; + *_num_rules = num_rules; + + return EOK; +} + +static void +sudosrv_dp_oob_req_done(struct tevent_req *req) +{ + DEBUG(SSSDBG_TRACE_FUNC, "Out of band refresh finished\n"); + talloc_free(req); +} + +struct sudosrv_refresh_rules_state { + struct resp_ctx *rctx; + struct sss_domain_info *domain; + const char *username; +}; + +static void sudosrv_refresh_rules_done(struct tevent_req *subreq); + +static struct tevent_req * +sudosrv_refresh_rules_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct resp_ctx *rctx, + struct sss_domain_info *domain, + int threshold, + uid_t uid, + const char *username, + char **groups) +{ + struct sudosrv_refresh_rules_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + struct sysdb_attrs **rules; + uint32_t num_rules; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, + struct sudosrv_refresh_rules_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->rctx = rctx; + state->domain = domain; + state->username = username; + + ret = sudosrv_expired_rules(state, domain, uid, username, groups, + &rules, &num_rules); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to retrieve expired sudo rules [%d]: %s\n", + ret, strerror(ret)); + goto immediately; + } + + if (num_rules == 0) { + DEBUG(SSSDBG_TRACE_FUNC, "No expired rules were found for [%s@%s].\n", + username, domain->name); + ret = EOK; + goto immediately; + } + + DEBUG(SSSDBG_TRACE_INTERNAL, "Refreshing %d expired rules of [%s@%s]\n", + num_rules, username, domain->name); + + if (num_rules > threshold) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Rules threshold [%d] is reached, performing full refresh " + "instead.\n", threshold); + + subreq = sss_dp_get_sudoers_send(state, rctx, domain, false, + SSS_DP_SUDO_FULL_REFRESH, + username, 0, NULL); + } else { + subreq = sss_dp_get_sudoers_send(state, rctx, domain, false, + SSS_DP_SUDO_REFRESH_RULES, + username, num_rules, rules); + } + + if (subreq == NULL) { + ret = ENOMEM; + goto immediately; + } + + tevent_req_set_callback(subreq, sudosrv_refresh_rules_done, req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, ev); + + return req; +} + +static void sudosrv_refresh_rules_done(struct tevent_req *subreq) +{ + struct sudosrv_refresh_rules_state *state; + struct tevent_req *req; + dbus_uint16_t err_maj; + dbus_uint32_t err_min; + const char *err_msg; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sudosrv_refresh_rules_state); + + ret = sss_dp_get_sudoers_recv(state, subreq, &err_maj, &err_min, &err_msg); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to refresh rules [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } else if (err_maj != 0 || err_min != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to get information from Data Provider, " + "Error: %u, %u, %s\n", + (unsigned int)err_maj, (unsigned int)err_min, + (err_msg == NULL ? "(null)" : err_msg)); + goto done; + } + + if (err_min == ENOENT) { + DEBUG(SSSDBG_TRACE_INTERNAL, + "Some expired rules were removed from the server, scheduling " + "full refresh out of band\n"); + subreq = sss_dp_get_sudoers_send(state->rctx, state->rctx, + state->domain, false, + SSS_DP_SUDO_FULL_REFRESH, + state->username, 0, NULL); + if (subreq == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "Cannot issue DP request.\n"); + ret = EOK; /* We don't care. */ + goto done; + } + + tevent_req_set_callback(subreq, sudosrv_dp_oob_req_done, NULL); + } + + ret = EOK; + +done: + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static errno_t sudosrv_refresh_rules_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +struct sudosrv_get_rules_state { + struct tevent_context *ev; + struct resp_ctx *rctx; + enum sss_sudo_type type; + uid_t cli_uid; + const char *username; + struct sss_domain_info *domain; + char **groups; + bool inverse_order; + int threshold; + + uid_t orig_uid; + const char *orig_username; + + struct sysdb_attrs **rules; + uint32_t num_rules; +}; + +static void sudosrv_get_rules_initgr_done(struct tevent_req *subreq); +static void sudosrv_get_rules_done(struct tevent_req *subreq); + +struct tevent_req *sudosrv_get_rules_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sudo_ctx *sudo_ctx, + enum sss_sudo_type type, + uid_t cli_uid, + const char *username) +{ + struct sudosrv_get_rules_state *state; + struct tevent_req *req; + struct tevent_req *subreq; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct sudosrv_get_rules_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + state->ev = ev; + state->rctx = sudo_ctx->rctx; + state->type = type; + state->cli_uid = cli_uid; + state->inverse_order = sudo_ctx->inverse_order; + state->threshold = sudo_ctx->threshold; + + DEBUG(SSSDBG_TRACE_FUNC, "Running initgroups for [%s]\n", username); + + subreq = cache_req_initgr_by_name_send(state, ev, sudo_ctx->rctx, + sudo_ctx->rctx->ncache, 0, + CACHE_REQ_POSIX_DOM, NULL, + username); + if (subreq == NULL) { + ret = ENOMEM; + tevent_req_error(req, ret); + tevent_req_post(req, ev); + } else { + tevent_req_set_callback(subreq, sudosrv_get_rules_initgr_done, req); + } + + return req; +} + +static void sudosrv_get_rules_initgr_done(struct tevent_req *subreq) +{ + struct sudosrv_get_rules_state *state; + struct cache_req_result *result; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sudosrv_get_rules_state); + + ret = cache_req_initgr_by_name_recv(state, subreq, &result); + talloc_zfree(subreq); + if (ret != EOK) { + goto done; + } + + state->domain = result->domain; + state->username = talloc_steal(state, result->lookup_name); + talloc_zfree(result); + + ret = sysdb_get_sudo_user_info(state, state->domain, state->username, + &state->orig_username, + &state->orig_uid, + &state->groups); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to obtain user groups [%d]: %s\n", + ret, sss_strerror(ret)); + goto done; + } + + subreq = sudosrv_refresh_rules_send(state, state->ev, state->rctx, + state->domain, state->threshold, + state->orig_uid, + state->orig_username, + state->groups); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sudosrv_get_rules_done, req); + + ret = EAGAIN; + +done: + if (ret != EOK && ret != EAGAIN) { + tevent_req_error(req, ret); + return; + } else if (ret != EAGAIN) { + tevent_req_done(req); + } +} + +static void sudosrv_get_rules_done(struct tevent_req *subreq) +{ + struct sudosrv_get_rules_state *state = NULL; + struct tevent_req *req = NULL; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct sudosrv_get_rules_state); + + ret = sudosrv_refresh_rules_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unable to refresh expired rules, we will return what is " + "in cache.\n"); + } + + ret = sudosrv_fetch_rules(state, state->rctx, state->type, state->domain, + state->cli_uid, + state->orig_uid, + state->orig_username, + state->groups, + state->inverse_order, + &state->rules, &state->num_rules); + + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t sudosrv_get_rules_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct sysdb_attrs ***_rules, + uint32_t *_num_rules) +{ + struct sudosrv_get_rules_state *state = NULL; + state = tevent_req_data(req, struct sudosrv_get_rules_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *_rules = talloc_steal(mem_ctx, state->rules); + *_num_rules = state->num_rules; + + return EOK; +} diff --git a/src/responder/sudo/sudosrv_private.h b/src/responder/sudo/sudosrv_private.h new file mode 100644 index 0000000..157afaa --- /dev/null +++ b/src/responder/sudo/sudosrv_private.h @@ -0,0 +1,112 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2011 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/>. +*/ + +#ifndef _SUDOSRV_PRIVATE_H_ +#define _SUDOSRV_PRIVATE_H_ + +#include <stdint.h> +#include <talloc.h> +#include <sys/types.h> + +#include "src/db/sysdb.h" +#include "responder/common/responder.h" + +#define SSS_SUDO_ERROR_OK 0 + +enum sss_dp_sudo_type { + SSS_DP_SUDO_REFRESH_RULES, + SSS_DP_SUDO_FULL_REFRESH +}; + +enum sss_sudo_type { + SSS_SUDO_DEFAULTS, + SSS_SUDO_USER +}; + +struct sudo_ctx { + struct resp_ctx *rctx; + + /* + * options + */ + bool timed; + bool inverse_order; + int threshold; +}; + +struct sudo_cmd_ctx { + struct cli_ctx *cli_ctx; + struct sudo_ctx *sudo_ctx; + enum sss_sudo_type type; + + /* input data */ + uid_t uid; + char *rawname; + + /* output data */ + struct sysdb_attrs **rules; + uint32_t num_rules; +}; + +struct sss_cmd_table *get_sudo_cmds(void); + +struct tevent_req *sudosrv_get_rules_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sudo_ctx *sudo_ctx, + enum sss_sudo_type type, + uid_t cli_uid, + const char *username); + +errno_t sudosrv_get_rules_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct sysdb_attrs ***_rules, + uint32_t *_num_rules); + +errno_t sudosrv_parse_query(TALLOC_CTX *mem_ctx, + uint8_t *query_body, + size_t query_len, + char **_rawname, + uid_t *_uid); + +errno_t sudosrv_build_response(TALLOC_CTX *mem_ctx, + uint32_t error, + uint32_t rules_num, + struct sysdb_attrs **rules, + uint8_t **_response_body, + size_t *_response_len); + +struct tevent_req * +sss_dp_get_sudoers_send(TALLOC_CTX *mem_ctx, + struct resp_ctx *rctx, + struct sss_domain_info *dom, + bool fast_reply, + enum sss_dp_sudo_type type, + const char *name, + uint32_t num_rules, + struct sysdb_attrs **rules); + +errno_t +sss_dp_get_sudoers_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + uint16_t *_dp_error, + uint32_t *_error, + const char ** _error_message); + +#endif /* _SUDOSRV_PRIVATE_H_ */ diff --git a/src/responder/sudo/sudosrv_query.c b/src/responder/sudo/sudosrv_query.c new file mode 100644 index 0000000..a868ebe --- /dev/null +++ b/src/responder/sudo/sudosrv_query.c @@ -0,0 +1,307 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2011 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 <string.h> +#include <stdint.h> +#include <errno.h> +#include <talloc.h> +#include <tevent.h> + +#include "util/util.h" +#include "responder/sudo/sudosrv_private.h" + +static int sudosrv_response_append_string(TALLOC_CTX *mem_ctx, + const char *str, + size_t str_len, + uint8_t **_response_body, + size_t *_response_len) +{ + size_t response_len = *_response_len; + uint8_t *response_body = *_response_body; + + response_body = talloc_realloc(mem_ctx, response_body, uint8_t, + response_len + (str_len * sizeof(char))); + if (response_body == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_realloc() failed\n"); + return ENOMEM; + } + memcpy(response_body + response_len, str, str_len); + response_len += str_len; + + *_response_body = response_body; + *_response_len = response_len; + + return EOK; +} + +static int sudosrv_response_append_uint32(TALLOC_CTX *mem_ctx, + uint32_t number, + uint8_t **_response_body, + size_t *_response_len) +{ + size_t response_len = *_response_len; + uint8_t *response_body = *_response_body; + + response_body = talloc_realloc(mem_ctx, response_body, uint8_t, + response_len + sizeof(uint32_t)); + if (response_body == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_realloc() failed\n"); + return ENOMEM; + } + SAFEALIGN_SET_UINT32(response_body + response_len, number, &response_len); + + *_response_body = response_body; + *_response_len = response_len; + + return EOK; +} + +static int sudosrv_response_append_attr(TALLOC_CTX *mem_ctx, + const char *name, + unsigned int values_num, + struct ldb_val *values, + uint8_t **_response_body, + size_t *_response_len) +{ + uint8_t *response_body = *_response_body; + size_t response_len = *_response_len; + TALLOC_CTX *tmp_ctx = NULL; + unsigned int i = 0; + int ret = EOK; + const char *strval; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + /* attr name */ + ret = sudosrv_response_append_string(tmp_ctx, name, strlen(name) + 1, + &response_body, &response_len); + if (ret != EOK) { + goto done; + } + + /* values count */ + ret = sudosrv_response_append_uint32(tmp_ctx, values_num, + &response_body, &response_len); + if (ret != EOK) { + goto done; + } + + /* values */ + for (i = 0; i < values_num; i++) { + strval = (const char *) values[i].data; + + if (strlen((strval)) != values[i].length) { + DEBUG(SSSDBG_CRIT_FAILURE, "value is not a string\n"); + ret = EINVAL; + goto done; + } + + ret = sudosrv_response_append_string(tmp_ctx, + strval, + values[i].length + 1, + &response_body, &response_len); + DEBUG(SSSDBG_TRACE_INTERNAL, "%s:%s\n", name, strval); + if (ret != EOK) { + goto done; + } + } + + *_response_body = talloc_steal(mem_ctx, response_body); + *_response_len = response_len; + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static int sudosrv_response_append_rule(TALLOC_CTX *mem_ctx, + int attrs_num, + struct ldb_message_element *attrs, + uint8_t **_response_body, + size_t *_response_len) +{ + uint8_t *response_body = *_response_body; + size_t response_len = *_response_len; + TALLOC_CTX *tmp_ctx = NULL; + int i = 0; + int ret = EOK; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + /* attrs count */ + ret = sudosrv_response_append_uint32(tmp_ctx, attrs_num, + &response_body, &response_len); + if (ret != EOK) { + goto done; + } + + /* attrs */ + for (i = 0; i < attrs_num; i++) { + ret = sudosrv_response_append_attr(tmp_ctx, attrs[i].name, + attrs[i].num_values, attrs[i].values, + &response_body, &response_len); + if (ret != EOK) { + goto done; + } + } + + *_response_body = talloc_steal(mem_ctx, response_body); + *_response_len = response_len; + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +/* + * Response format: + * <error_code(uint32_t)><domain(char*)>\0<num_entries(uint32_t)><rule1><rule2>... + * <ruleN> = <num_attrs(uint32_t)><attr1><attr2>... + * <attrN> = <name(char*)>\0<num_values(uint32_t)><value1(char*)>\0<value2(char*)>\0... + * + * if <error_code> is not SSS_SUDO_ERROR_OK, the rest of the data is skipped. + */ +errno_t sudosrv_build_response(TALLOC_CTX *mem_ctx, + uint32_t error, + uint32_t rules_num, + struct sysdb_attrs **rules, + uint8_t **_response_body, + size_t *_response_len) +{ + uint8_t *response_body = NULL; + size_t response_len = 0; + TALLOC_CTX *tmp_ctx = NULL; + uint32_t i = 0; + errno_t ret = EOK; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n"); + return ENOMEM; + } + + /* error code */ + ret = sudosrv_response_append_uint32(tmp_ctx, error, + &response_body, &response_len); + if (ret != EOK) { + goto fail; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "error: [%"PRIu32"]\n", error); + + if (error != SSS_SUDO_ERROR_OK) { + goto done; + } + + /* domain name - deprecated + * TODO: when possible change the protocol */ + ret = sudosrv_response_append_string(tmp_ctx, "\0", 1, + &response_body, &response_len); + if (ret != EOK) { + goto fail; + } + + /* rules count */ + ret = sudosrv_response_append_uint32(tmp_ctx, rules_num, + &response_body, &response_len); + if (ret != EOK) { + goto fail; + } + DEBUG(SSSDBG_TRACE_INTERNAL, "rules_num: [%"PRIu32"]\n", rules_num); + + /* rules */ + for (i = 0; i < rules_num; i++) { + DEBUG(SSSDBG_TRACE_INTERNAL, "rule [%"PRIu32"]/[%"PRIu32"]\n", i+1, rules_num); + ret = sudosrv_response_append_rule(tmp_ctx, rules[i]->num, rules[i]->a, + &response_body, &response_len); + if (ret != EOK) { + goto fail; + } + } + +done: + *_response_body = talloc_steal(mem_ctx, response_body); + *_response_len = response_len; + + ret = EOK; + +fail: + talloc_free(tmp_ctx); + return ret; +} + +errno_t sudosrv_parse_query(TALLOC_CTX *mem_ctx, + uint8_t *query_body, + size_t query_len, + char **_rawname, + uid_t *_uid) +{ + size_t offset = 0; + size_t rawname_len; + char *rawname; + uid_t uid; + + /* uid */ + if (query_len < sizeof(uid_t)) { + DEBUG(SSSDBG_CRIT_FAILURE, "Query is too small\n"); + return EINVAL; + } + safealign_memcpy(&uid, query_body, sizeof(uid_t), &offset); + + /* username[@domain] */ + rawname = (char*)(query_body + offset); + rawname_len = query_len - offset; /* strlen + zero */ + + if (rawname[rawname_len - 1] != '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, "Username is not zero terminated\n"); + return EINVAL; + } + + if (rawname_len < 2) { /* at least one character and zero */ + DEBUG(SSSDBG_CRIT_FAILURE, "Query does not contain username\n"); + return EINVAL; + } + + if (!sss_utf8_check((uint8_t*)rawname, rawname_len - 1)) { + DEBUG(SSSDBG_CRIT_FAILURE, "Supplied data is not valid UTF-8 string\n"); + return EINVAL; + } + + rawname = talloc_strdup(mem_ctx, rawname); + if (rawname == NULL) { + return ENOMEM; + } + + *_uid = uid; + *_rawname = rawname; + + return EOK; +} |