diff options
-rw-r--r-- | src/responder/ifp/ifp_iface.xml | 9 | ||||
-rw-r--r-- | src/responder/ifp/ifp_iface_generated.c | 20 | ||||
-rw-r--r-- | src/responder/ifp/ifp_iface_generated.h | 2 | ||||
-rw-r--r-- | src/responder/ifp/ifp_private.h | 2 | ||||
-rw-r--r-- | src/responder/ifp/ifpsrv.c | 1 | ||||
-rw-r--r-- | src/responder/ifp/ifpsrv_cmd.c | 563 |
6 files changed, 597 insertions, 0 deletions
diff --git a/src/responder/ifp/ifp_iface.xml b/src/responder/ifp/ifp_iface.xml index 078d29e2d..de6acce2f 100644 --- a/src/responder/ifp/ifp_iface.xml +++ b/src/responder/ifp/ifp_iface.xml @@ -3,9 +3,18 @@ <node> <interface name="org.freedesktop.sssd.infopipe"> <annotation value="infopipe_iface" name="org.freedesktop.DBus.GLib.CSymbol"/> + <method name="Ping"> <!-- manual argument parsing, raw handler --> <annotation name="org.freedesktop.sssd.RawHandler" value="true"/> </method> + + <method name="GetUserAttr"> + <arg name="user" type="s" direction="in" /> + <arg name="attr" type="as" direction="in" /> + <arg name="values" type="a{sv}" direction="out"/> + <annotation name="org.freedesktop.sssd.RawHandler" value="true"/> + </method> + </interface> </node> diff --git a/src/responder/ifp/ifp_iface_generated.c b/src/responder/ifp/ifp_iface_generated.c index 57c67f8bc..ed8d65187 100644 --- a/src/responder/ifp/ifp_iface_generated.c +++ b/src/responder/ifp/ifp_iface_generated.c @@ -5,6 +5,19 @@ #include "sbus/sssd_dbus_meta.h" #include "ifp_iface_generated.h" +/* arguments for org.freedesktop.sssd.infopipe.GetUserAttr */ +const struct sbus_arg_meta infopipe_iface_GetUserAttr__in[] = { + { "user", "s" }, + { "attr", "as" }, + { NULL, } +}; + +/* arguments for org.freedesktop.sssd.infopipe.GetUserAttr */ +const struct sbus_arg_meta infopipe_iface_GetUserAttr__out[] = { + { "values", "a{sv}" }, + { NULL, } +}; + /* methods for org.freedesktop.sssd.infopipe */ const struct sbus_method_meta infopipe_iface__methods[] = { { @@ -14,6 +27,13 @@ const struct sbus_method_meta infopipe_iface__methods[] = { offsetof(struct infopipe_iface, Ping), NULL, /* no invoker */ }, + { + "GetUserAttr", /* name */ + infopipe_iface_GetUserAttr__in, + infopipe_iface_GetUserAttr__out, + offsetof(struct infopipe_iface, GetUserAttr), + NULL, /* no invoker */ + }, { NULL, } }; diff --git a/src/responder/ifp/ifp_iface_generated.h b/src/responder/ifp/ifp_iface_generated.h index f69fb162d..c52e87f06 100644 --- a/src/responder/ifp/ifp_iface_generated.h +++ b/src/responder/ifp/ifp_iface_generated.h @@ -14,6 +14,7 @@ /* constants for org.freedesktop.sssd.infopipe */ #define INFOPIPE_IFACE "org.freedesktop.sssd.infopipe" #define INFOPIPE_IFACE_PING "Ping" +#define INFOPIPE_IFACE_GETUSERATTR "GetUserAttr" /* ------------------------------------------------------------------------ * DBus handlers @@ -37,6 +38,7 @@ struct infopipe_iface { struct sbus_vtable vtable; /* derive from sbus_vtable */ sbus_msg_handler_fn Ping; + sbus_msg_handler_fn GetUserAttr; }; /* ------------------------------------------------------------------------ diff --git a/src/responder/ifp/ifp_private.h b/src/responder/ifp/ifp_private.h index e44b27bf4..52c480bb4 100644 --- a/src/responder/ifp/ifp_private.h +++ b/src/responder/ifp/ifp_private.h @@ -49,6 +49,8 @@ struct ifp_ctx { * It will be removed later */ int ifp_ping(struct sbus_request *dbus_req, void *data); +int ifp_user_get_attr(struct sbus_request *dbus_req, void *data); + /* == Utility functions == */ struct ifp_req { struct sbus_request *dbus_req; diff --git a/src/responder/ifp/ifpsrv.c b/src/responder/ifp/ifpsrv.c index f9dc69057..978f3614a 100644 --- a/src/responder/ifp/ifpsrv.c +++ b/src/responder/ifp/ifpsrv.c @@ -66,6 +66,7 @@ static struct data_provider_iface ifp_dp_methods = { struct infopipe_iface ifp_iface = { { &infopipe_iface_meta, 0 }, .Ping = ifp_ping, + .GetUserAttr = ifp_user_get_attr, }; struct sss_cmd_table *get_ifp_cmds(void) diff --git a/src/responder/ifp/ifpsrv_cmd.c b/src/responder/ifp/ifpsrv_cmd.c index e26bcfa58..2fc4308b4 100644 --- a/src/responder/ifp/ifpsrv_cmd.c +++ b/src/responder/ifp/ifpsrv_cmd.c @@ -20,8 +20,571 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#include "db/sysdb.h" + #include "responder/ifp/ifp_private.h" +struct ifp_attr_req { + const char *name; + const char **attrs; + int nattrs; + + struct ifp_req *ireq; +}; + +static struct tevent_req * +ifp_user_get_attr_send(TALLOC_CTX *mem_ctx, struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, int neg_timeout, + const char *inp, const char **attrs); +static errno_t ifp_user_get_attr_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ldb_result **_res); + +static void ifp_user_get_attr_process(struct tevent_req *req); + +static errno_t +ifp_user_get_attr_handle_reply(struct ifp_req *ireq, const char *user, + const char **attrs, struct ldb_result *res); +static errno_t +ifp_user_get_attr_unpack_msg(struct ifp_attr_req *attr_req); + +int ifp_user_get_attr(struct sbus_request *dbus_req, void *data) +{ + errno_t ret; + struct ifp_req *ireq; + struct ifp_ctx *ifp_ctx; + struct ifp_attr_req *attr_req; + struct tevent_req *req; + + ifp_ctx = talloc_get_type(data, struct ifp_ctx); + if (ifp_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid pointer!\n"); + return sbus_request_return_and_finish(dbus_req, DBUS_TYPE_INVALID); + } + + ret = ifp_req_create(dbus_req, ifp_ctx, &ireq); + if (ret != EOK) { + return ifp_req_create_handle_failure(dbus_req, ret); + } + + attr_req = talloc_zero(ireq, struct ifp_attr_req); + if (attr_req == NULL) { + return sbus_request_finish(dbus_req, NULL); + } + attr_req->ireq = ireq; + + ret = ifp_user_get_attr_unpack_msg(attr_req); + if (ret != EOK) { + return ret; /* handled internally */ + } + + DEBUG(SSSDBG_FUNC_DATA, + "Looking up attributes of user [%s] on behalf of %"PRIi64"\n", + attr_req->name, ireq->dbus_req->client); + + req = ifp_user_get_attr_send(ireq, ifp_ctx->rctx, + ifp_ctx->ncache, ifp_ctx->neg_timeout, + attr_req->name, attr_req->attrs); + if (req == NULL) { + return sbus_request_finish(dbus_req, NULL); + } + tevent_req_set_callback(req, ifp_user_get_attr_process, attr_req); + return EOK; +} + +static errno_t +ifp_user_get_attr_unpack_msg(struct ifp_attr_req *attr_req) +{ + bool parsed; + + parsed = sbus_request_parse_or_finish(attr_req->ireq->dbus_req, + DBUS_TYPE_STRING, &attr_req->name, + DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, + &attr_req->attrs, + &attr_req->nattrs, + DBUS_TYPE_INVALID); + if (parsed == false) { + return EOK; /* handled */ + } + + return EOK; +} + +static void ifp_user_get_attr_process(struct tevent_req *req) +{ + struct ifp_attr_req *attr_req; + errno_t ret; + struct ldb_result *res = NULL; + + attr_req = tevent_req_callback_data(req, struct ifp_attr_req); + + ret = ifp_user_get_attr_recv(attr_req, req, &res); + talloc_zfree(req); + if (ret == ENOENT) { + sbus_request_fail_and_finish(attr_req->ireq->dbus_req, + sbus_error_new(attr_req->ireq->dbus_req, + DBUS_ERROR_FAILED, + "No such user\n")); + return; + } else if (ret != EOK) { + sbus_request_fail_and_finish(attr_req->ireq->dbus_req, + sbus_error_new(attr_req->ireq->dbus_req, + DBUS_ERROR_FAILED, + "Failed to read user attribute\n")); + return; + } + + ret = ifp_user_get_attr_handle_reply(attr_req->ireq, attr_req->name, + attr_req->attrs, res); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Could not handle reply!\n"); + /* Nothing to do, let the client time out */ + return; + } +} + +static errno_t +ifp_user_get_attr_handle_reply(struct ifp_req *ireq, const char *user, + const char **attrs, struct ldb_result *res) +{ + errno_t ret; + dbus_bool_t dbret; + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter iter_dict; + struct ldb_message_element *el; + int ai; + + /* Construct a reply */ + reply = dbus_message_new_method_return(ireq->dbus_req->message); + if (!reply) { + return sbus_request_finish(ireq->dbus_req, NULL); + } + + dbus_message_iter_init_append(reply, &iter); + + 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 sbus_request_finish(ireq->dbus_req, NULL); + } + + if (res->count > 0) { + for (ai = 0; attrs[ai]; ai++) { + el = ldb_msg_find_element(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) { + return sbus_request_finish(ireq->dbus_req, NULL); + } + + return sbus_request_finish(ireq->dbus_req, reply); +} + +struct ifp_user_get_attr_state { + const char *inp; + const char **attrs; + struct ldb_result *res; + + char *name; + char *domname; + + struct sss_domain_info *dom; + bool check_next; + bool check_provider; + + struct resp_ctx *rctx; + struct sss_nc_ctx *ncache; + int neg_timeout; +}; + +static void ifp_user_get_attr_dom(struct tevent_req *subreq); +static errno_t ifp_user_get_attr_search(struct tevent_req *req); +int ifp_cache_check(struct ifp_user_get_attr_state *state, + sss_dp_callback_t callback, + unsigned int cache_refresh_percent, + void *pvt); +void ifp_user_get_attr_done(struct tevent_req *req); + +static struct tevent_req * +ifp_user_get_attr_send(TALLOC_CTX *mem_ctx, struct resp_ctx *rctx, + struct sss_nc_ctx *ncache, int neg_timeout, + const char *inp, const char **attrs) +{ + errno_t ret; + struct tevent_req *req; + struct tevent_req *subreq; + struct ifp_user_get_attr_state *state; + + req = tevent_req_create(mem_ctx, &state, struct ifp_user_get_attr_state); + if (req == NULL) { + return NULL; + } + state->inp = inp; + state->attrs = attrs; + state->rctx = rctx; + state->ncache = ncache; + state->neg_timeout = neg_timeout; + + subreq = sss_parse_inp_send(req, rctx, inp); + if (subreq == NULL) { + ret = ENOMEM; + goto done; + } + tevent_req_set_callback(subreq, ifp_user_get_attr_dom, req); + + ret = EOK; +done: + if (ret != EOK) { + tevent_req_error(req, ret); + } + return req; +} + +static void +ifp_user_get_attr_dom(struct tevent_req *subreq) +{ + errno_t ret; + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct ifp_user_get_attr_state *state = tevent_req_data(req, + struct ifp_user_get_attr_state); + + ret = sss_parse_inp_recv(subreq, state, &state->name, &state->domname); + talloc_free(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + if (state->domname) { + /* this is a search in one domain */ + state->dom = responder_get_domain(state->rctx, state->domname); + if (state->dom == NULL) { + tevent_req_error(req, EINVAL); + return; + } + state->check_next = false; + } else { + /* this is a multidomain search */ + state->dom = state->rctx->domains; + state->check_next = true; + } + + state->check_provider = NEED_CHECK_PROVIDER(state->dom->provider); + + /* All set up, do the search! */ + ret = ifp_user_get_attr_search(req); + if (ret == EOK) { + /* The data was cached. Just quit */ + tevent_req_done(req); + return; + } else if (ret != EAGAIN) { + tevent_req_error(req, ret); + return; + } + + /* Execution will resume in ifp_dp_callback */ +} + +static void ifp_dp_callback(uint16_t err_maj, uint32_t err_min, + const char *err_msg, void *ptr); + +static errno_t ifp_user_get_attr_search(struct tevent_req *req) +{ + struct ifp_user_get_attr_state *state = tevent_req_data(req, + struct ifp_user_get_attr_state); + struct sss_domain_info *dom = state->dom; + char *name = NULL; + errno_t ret; + + while (dom) { + /* if it is a domainless search, skip domains that require fully + * qualified names instead */ + while (dom && state->check_next && dom->fqnames) { + dom = get_next_domain(dom, false); + } + + if (!dom) break; + + if (dom != state->dom) { + /* make sure we reset the check_provider flag when we check + * a new domain */ + state->check_provider = NEED_CHECK_PROVIDER(dom->provider); + } + + /* make sure to update the cache_req if we changed domain */ + state->dom = dom; + + talloc_free(name); + name = sss_get_cased_name(state, state->name, dom->case_sensitive); + if (!name) return ENOMEM; + + /* verify this user has not yet been negatively cached, + * or has been permanently filtered */ + ret = sss_ncache_check_user(state->ncache, + state->neg_timeout, + dom, name); + /* if neg cached, return we didn't find it */ + if (ret == EEXIST) { + DEBUG(SSSDBG_TRACE_FUNC, + "User [%s] does not exist in [%s]! (negative cache)\n", + name, dom->name); + /* if a multidomain search, try with next */ + if (state->check_next) { + dom = get_next_domain(dom, false); + continue; + } + + /* There are no further domains or this was a + * fully-qualified user request. + */ + return ENOENT; + } + + DEBUG(SSSDBG_FUNC_DATA, + "Requesting info for [%s@%s]\n", name, dom->name); + + ret = sysdb_get_user_attr(state, dom, name, state->attrs, &state->res); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to make request to our cache!\n"); + return EIO; + } + + if (state->res->count > 1) { + DEBUG(SSSDBG_CRIT_FAILURE, + "getpwnam call returned more than one result !?!\n"); + return ENOENT; + } + + if (state->res->count == 0 && state->check_provider == false) { + /* set negative cache only if not result of cache check */ + ret = sss_ncache_set_user(state->ncache, false, dom, name); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, "Cannot set negcache for %s@%s\n", + name, dom->name); + /* Not fatal */ + } + + /* if a multidomain search, try with next */ + if (state->check_next) { + dom = get_next_domain(dom, false); + if (dom) continue; + } + + DEBUG(SSSDBG_TRACE_FUNC, "No results for getpwnam call\n"); + return ENOENT; + } + + /* if this is a caching provider (or if we haven't checked the cache + * yet) then verify that the cache is uptodate */ + if (state->check_provider) { + ret = ifp_cache_check(state, ifp_dp_callback, 0, req); + if (ret != EOK) { + /* Anything but EOK means we should reenter the mainloop + * because we may be refreshing the cache + */ + return ret; + } + } + + /* One result found */ + DEBUG(SSSDBG_TRACE_FUNC, + "Returning info for user [%s@%s]\n", name, dom->name); + return EOK; + } + + DEBUG(SSSDBG_MINOR_FAILURE, + "No matching domain found for [%s], fail!\n", state->inp); + return ENOENT; +} + +int ifp_cache_check(struct ifp_user_get_attr_state *state, + sss_dp_callback_t callback, + unsigned int cache_refresh_percent, + void *pvt) +{ + uint64_t cache_expire = 0; + int ret; + struct tevent_req *req; + struct dp_callback_ctx *cb_ctx = NULL; + + if (state->res->count > 1) { + DEBUG(SSSDBG_OP_FAILURE, + "cache search call returned more than one result! " + "DB Corrupted?\n"); + return ENOENT; + } + + if (state->res->count > 0) { + cache_expire = ldb_msg_find_attr_as_uint64(state->res->msgs[0], + SYSDB_CACHE_EXPIRE, 0); + + /* if we have any reply let's check cache validity */ + ret = sss_cmd_check_cache(state->res->msgs[0], cache_refresh_percent, + cache_expire); + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "Cached entry is valid, returning..\n"); + return EOK; + } else if (ret != EAGAIN && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "Error checking cache: %d\n", ret); + return ret; + } + } else { + /* No replies */ + ret = ENOENT; + } + + /* EAGAIN (off band) or ENOENT (cache miss) -> check cache */ + if (ret == EAGAIN) { + /* No callback required + * This was an out-of-band update. We'll return EOK + * so the calling function can return the cached entry + * immediately. + */ + DEBUG(SSSDBG_TRACE_FUNC, "Performing midpoint cache update\n"); + + req = sss_dp_get_account_send(state, state->rctx, state->dom, true, + SSS_DP_USER, state->inp, 0, + NULL); + if (req == 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 { + DEBUG(SSSDBG_TRACE_FUNC, "Updating cache out-of-band\n"); + } + + /* We don't need to listen for a reply, so we will free the + * request here. + */ + talloc_zfree(req); + } else { + /* This is a cache miss. Or the cache is expired. + * We need to get the updated user information before returning it. + */ + + /* dont loop forever; mark the provider as checked */ + state->check_provider = false; + + req = sss_dp_get_account_send(state, state->rctx, state->dom, true, + SSS_DP_USER, state->inp, 0, NULL); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Out of memory sending data provider request\n"); + return ENOMEM; + } + + cb_ctx = talloc_zero(state, struct dp_callback_ctx); + if (cb_ctx == NULL) { + talloc_zfree(req); + return ENOMEM; + } + cb_ctx->callback = callback; + cb_ctx->ptr = pvt; + cb_ctx->cctx = NULL; /* There is no client in ifp */ + cb_ctx->mem_ctx = state; + + tevent_req_set_callback(req, ifp_user_get_attr_done, cb_ctx); + return EAGAIN; + } + + return EOK; +} + +void ifp_user_get_attr_done(struct tevent_req *req) +{ + struct dp_callback_ctx *cb_ctx = + tevent_req_callback_data(req, struct dp_callback_ctx); + + errno_t ret; + dbus_uint16_t err_maj; + dbus_uint32_t err_min; + char *err_msg; + + ret = sss_dp_get_account_recv(cb_ctx->mem_ctx, req, + &err_maj, &err_min, + &err_msg); + talloc_zfree(req); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Could not get account info: %d\n", ret); + /* report error with callback */ + } + + cb_ctx->callback(err_maj, err_min, err_msg, cb_ctx->ptr); +} + +static void ifp_dp_callback(uint16_t err_maj, uint32_t err_min, + const char *err_msg, void *ptr) +{ + errno_t ret; + struct tevent_req *req = talloc_get_type(ptr, struct tevent_req); + + if (err_maj) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Unable to get information from Data Provider\n" + "Error: %u, %u, %s\n" + "Will try to return what we have in cache\n", + (unsigned int)err_maj, (unsigned int)err_min, err_msg); + } + + /* Backend was updated successfully. Check again */ + ret = ifp_user_get_attr_search(req); + if (ret == EAGAIN) { + /* Another search in progress */ + return; + } else if (ret != EOK) { + tevent_req_error(req, ret); + 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 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); + } + return EOK; +} + struct cli_protocol_version *register_cli_protocol_version(void) { static struct cli_protocol_version ssh_cli_protocol_version[] = { |