From 97263498f47e0225c37b3f98476f03dcd34d9b86 Mon Sep 17 00:00:00 2001 From: Jakub Hrozek Date: Mon, 22 Jul 2013 09:55:28 +0200 Subject: NSS responder: interface to query custom attributes --- src/responder/nss/nsssrv_cmd.c | 354 +++++++++++++++++++++++++++++++++++-- src/responder/nss/nsssrv_private.h | 1 + 2 files changed, 339 insertions(+), 16 deletions(-) diff --git a/src/responder/nss/nsssrv_cmd.c b/src/responder/nss/nsssrv_cmd.c index 62a564a41..2879de613 100644 --- a/src/responder/nss/nsssrv_cmd.c +++ b/src/responder/nss/nsssrv_cmd.c @@ -491,6 +491,59 @@ static int nss_cmd_getpw_send_reply(struct nss_dom_ctx *dctx, bool filter) return EOK; } +static int get_user_attr_parse(uint8_t *body, size_t blen, const char **_rawname, + size_t *_namelen, uint8_t **_pvt) +{ + const char *rawname = NULL; + uint8_t *pvt = NULL; + size_t bp; + uint32_t len, namelen, pvtlen; + + /* The packet contains: + * total packet length (32 bytes) + * length of the username (32 bytes) + * username (zero terminated) + * length of the attribute name (32 bytes) + * attribute name (zero terminated) + */ + bp = 0; + SAFEALIGN_BUF2VAR_UINT32(&len, body+bp, &bp); + if (len > blen) { + return EIO; + } + + SAFEALIGN_BUF2VAR_UINT32(&namelen, body+bp, &bp); + if (namelen > len - bp) { + return EIO; + } + + if (namelen == 0) { + /* No name? Punt. */ + *_rawname = NULL; + return EINVAL; + } + + if (body[namelen-1] != '\0') { + return EINVAL; + } + + rawname = (const char *) body + bp; + bp += namelen; + + SAFEALIGN_BUF2VAR_UINT32(&pvtlen, body+bp, &bp); + /* Sanity check if pvt data is within the boundaries */ + if (pvtlen > len - bp) { + return EIO; + } + + pvt = body + bp; + + *_rawname = rawname; + *_namelen = namelen; + *_pvt = pvt; + return EOK; +} + static void nsssrv_dp_send_acct_req_done(struct tevent_req *req); /* FIXME: do not check res->count, but get in a msgs and check in parent */ @@ -820,6 +873,211 @@ static int nss_cmd_getpwnam_search(struct nss_dom_ctx *dctx) return ENOENT; } +static errno_t fill_attr(struct sss_packet *packet, + const char *attrname, + struct ldb_message *msg) +{ + const char *attr_val_str; + struct sized_string attr_val; + uint8_t *body; + size_t blen, bp; + errno_t ret; + + attr_val_str = ldb_msg_find_attr_as_string(msg, attrname, NULL); + if (attr_val_str == NULL) { + return ENOENT; + } + + to_sized_string(&attr_val, attr_val_str); + + ret = sss_packet_grow(packet, attr_val.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, &blen); + + bp = 0; + SAFEALIGN_VAR2BUF_UINT32(body+bp, 1, &bp); /* num results */ + SAFEALIGN_VAR2BUF_UINT32(body+bp, 0, &bp); /* reserved */ + SAFEALIGN_VAR2BUF_UINT32(body+bp, SSS_ID_TYPE_ATTR, &bp); /* id type */ + memcpy(body + bp, attr_val.str, attr_val.len); + return EOK; +} + +static errno_t nss_cmd_getattr_send_reply(struct nss_dom_ctx *dctx) +{ + struct nss_cmd_ctx *cmdctx = dctx->cmdctx; + struct cli_ctx *cctx = cmdctx->cctx; + const char *attrname = (const char *) dctx->pvt; + errno_t ret; + + if (dctx->res->count > 1) { + return EINVAL; + } else if (dctx->res->count == 0) { + return ENOENT; + } + + ret = sss_packet_new(cctx->creq, 0, + sss_packet_get_cmd(cctx->creq->in), + &cctx->creq->out); + if (ret != EOK) { + return EFAULT; + } + + ret = fill_attr(cctx->creq->out, attrname, dctx->res->msgs[0]); + if (ret != EOK) { + return ret; + } + + sss_packet_set_error(cctx->creq->out, EOK); + sss_cmd_done(cctx, cmdctx); + return EOK; +} + +static int nss_cmd_getattr_search(struct nss_dom_ctx *dctx) +{ + struct nss_cmd_ctx *cmdctx = dctx->cmdctx; + struct sss_domain_info *dom = dctx->domain; + struct cli_ctx *cctx = cmdctx->cctx; + char *name = NULL; + struct sysdb_ctx *sysdb; + struct nss_ctx *nctx; + int ret; + struct ldb_message *msg; + const char *attrs[] = { SYSDB_NAME, + (const char *) dctx->pvt, + NULL}; + + nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); + + while (dom) { + /* if it is a domainless search, skip domains that require fully + * qualified names instead */ + while (dom && cmdctx->check_next && dom->fqnames) { + dom = get_next_domain(dom, false); + } + + if (!dom) break; + + if (dom != dctx->domain) { + /* make sure we reset the check_provider flag when we check + * a new domain */ + dctx->check_provider = NEED_CHECK_PROVIDER(dom->provider); + } + + /* make sure to update the dctx if we changed domain */ + dctx->domain = dom; + + talloc_free(name); + name = sss_get_cased_name(cmdctx, cmdctx->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(nctx->ncache, nctx->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 (cmdctx->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)); + + sysdb = dom->sysdb; + if (sysdb == NULL) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + return EIO; + } + + ret = sysdb_search_user_by_name(cmdctx, sysdb, dom, name, attrs, &msg); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, ("Failed to make request to our cache!\n")); + return EIO; + } + + dctx->res = talloc_zero(cmdctx, struct ldb_result); + if (dctx->res == NULL) { + DEBUG(SSSDBG_OP_FAILURE, ("talloc_zero failed.\n")); + return ENOMEM; + } + + if (ret == EOK) { + dctx->res->count = 1; + dctx->res->msgs = talloc_array(dctx->res, struct ldb_message *, 1); + if (dctx->res->msgs == NULL) { + DEBUG(SSSDBG_OP_FAILURE, ("talloc_array failed.\n")); + return ENOMEM; + } + dctx->res->msgs[0] = talloc_steal(dctx->res, msg); + } + + if (dctx->res->count == 0 && !dctx->check_provider) { + /* set negative cache only if not result of cache check */ + ret = sss_ncache_set_user(nctx->ncache, false, dom, name); + if (ret != EOK) { + return ret; + } + + /* if a multidomain search, try with next */ + if (cmdctx->check_next) { + dom = get_next_domain(dom, false); + if (dom) continue; + } + + DEBUG(2, ("No results for getpwnam call\n")); + + /* User not found in ldb -> delete user from memory cache. */ + ret = delete_entry_from_memcache(dctx->domain, name, + nctx->pwd_mc_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, + ("Deleting user from memcache failed.\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 (dctx->check_provider) { + ret = check_cache(dctx, nctx, dctx->res, + SSS_DP_USER, name, 0, + nss_cmd_getby_dp_callback, + dctx); + 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(6, ("Returning info for user [%s@%s]\n", name, dom->name)); + + return EOK; + } + + DEBUG(SSSDBG_MINOR_FAILURE, + ("No matching domain found for [%s], fail!\n", cmdctx->name)); + return ENOENT; +} + static int nss_cmd_getgrnam_search(struct nss_dom_ctx *dctx); static int nss_cmd_getgr_send_reply(struct nss_dom_ctx *dctx, bool filter); static int nss_cmd_initgroups_search(struct nss_dom_ctx *dctx); @@ -870,6 +1128,9 @@ static void nss_cmd_getby_dp_callback(uint16_t err_maj, uint32_t err_min, case SSS_NSS_GETSIDBYID: ret = nss_cmd_getbysid_send_reply(dctx); break; + case SSS_NSS_GET_USER_ATTR: + ret = nss_cmd_getattr_send_reply(dctx); + break; default: DEBUG(SSSDBG_CRIT_FAILURE, ("Invalid command [%d].\n", dctx->cmdctx->cmd)); @@ -960,6 +1221,12 @@ static void nss_cmd_getby_dp_callback(uint16_t err_maj, uint32_t err_min, ret = nss_cmd_getbysid_send_reply(dctx); } break; + case SSS_NSS_GET_USER_ATTR: + ret = nss_cmd_getattr_search(dctx); + if (ret == EOK) { + ret = nss_cmd_getattr_send_reply(dctx); + } + break; default: DEBUG(SSSDBG_CRIT_FAILURE, ("Invalid command [%d].\n", dctx->cmdctx->cmd)); @@ -974,6 +1241,8 @@ done: } static int nss_cmd_getbynam(enum sss_cli_command cmd, struct cli_ctx *cctx); +static int nss_cmd_getbynam_parse_inp(enum sss_cli_command cmd, struct cli_ctx *cctx, + const char **_rawname, uint8_t **_pvt); static void nss_cmd_getbynam_done(struct tevent_req *req); static int nss_cmd_getpwnam(struct cli_ctx *cctx) { @@ -987,9 +1256,8 @@ static int nss_cmd_getbynam(enum sss_cli_command cmd, struct cli_ctx *cctx) struct nss_cmd_ctx *cmdctx; struct nss_dom_ctx *dctx; const char *rawname; + uint8_t *pvt; char *domname; - uint8_t *body; - size_t blen; int ret; switch(cmd) { @@ -997,6 +1265,7 @@ static int nss_cmd_getbynam(enum sss_cli_command cmd, struct cli_ctx *cctx) case SSS_NSS_GETGRNAM: case SSS_NSS_INITGR: case SSS_NSS_GETSIDBYNAME: + case SSS_NSS_GET_USER_ATTR: break; default: DEBUG(SSSDBG_CRIT_FAILURE, ("Invalid command type [%d].\n", cmd)); @@ -1017,23 +1286,12 @@ static int nss_cmd_getbynam(enum sss_cli_command cmd, struct cli_ctx *cctx) } dctx->cmdctx = cmdctx; - /* get user name to query */ - sss_packet_get_body(cctx->creq->in, &body, &blen); - - /* if not terminated fail */ - if (body[blen -1] != '\0') { - ret = EINVAL; - goto done; - } - - /* If the body isn't valid UTF-8, fail */ - if (!sss_utf8_check(body, blen -1)) { - ret = EINVAL; + ret = nss_cmd_getbynam_parse_inp(cmd, cctx, &rawname, &pvt); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, ("Cannot parse input\n")); goto done; } - rawname = (const char *)body; - DEBUG(SSSDBG_TRACE_FUNC, ("Running command [%d] with input [%s].\n", dctx->cmdctx->cmd, rawname)); @@ -1047,6 +1305,7 @@ static int nss_cmd_getbynam(enum sss_cli_command cmd, struct cli_ctx *cctx) ret = ENOMEM; } else { dctx->rawname = rawname; + dctx->pvt = pvt; tevent_req_set_callback(req, nss_cmd_getbynam_done, dctx); ret = EAGAIN; } @@ -1069,6 +1328,7 @@ static int nss_cmd_getbynam(enum sss_cli_command cmd, struct cli_ctx *cctx) } else { /* this is a multidomain search */ dctx->rawname = rawname; + dctx->pvt = pvt; dctx->domain = cctx->rctx->domains; cmdctx->check_next = true; if (cctx->rctx->get_domains_last_call.tv_sec == 0) { @@ -1114,6 +1374,12 @@ static int nss_cmd_getbynam(enum sss_cli_command cmd, struct cli_ctx *cctx) ret = nss_cmd_getbysid_send_reply(dctx); } break; + case SSS_NSS_GET_USER_ATTR: + ret = nss_cmd_getattr_search(dctx); + if (ret == EOK) { + ret = nss_cmd_getattr_send_reply(dctx); + } + break; default: DEBUG(SSSDBG_CRIT_FAILURE, ("Invalid command [%d].\n", dctx->cmdctx->cmd)); @@ -1124,6 +1390,56 @@ done: return nss_cmd_done(cmdctx, ret); } +static int nss_cmd_getbynam_parse_inp(enum sss_cli_command cmd, struct cli_ctx *cctx, + const char **_rawname, uint8_t **_pvt) +{ + uint8_t *body; + size_t blen; + size_t namelen; + const char *rawname; + uint8_t *pvt = NULL; + errno_t ret; + + /* get user name to query */ + sss_packet_get_body(cctx->creq->in, &body, &blen); + + /* if not terminated fail */ + if (body[blen -1] != '\0') { + return EINVAL; + } + + switch(cmd) { + case SSS_NSS_GETPWNAM: + case SSS_NSS_GETGRNAM: + case SSS_NSS_INITGR: + case SSS_NSS_GETSIDBYNAME: + rawname = (const char *)body; + namelen = blen; + pvt = NULL; + break; + case SSS_NSS_GET_USER_ATTR: + ret = get_user_attr_parse(body, blen, &rawname, &namelen, &pvt); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + ("Maloformed packet for command %d\n", cmd)); + return EINVAL; + } + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, ("Invalid command type [%d].\n", cmd)); + return EINVAL; + } + + /* If the name isn't valid UTF-8, fail */ + if (!sss_utf8_check((const uint8_t *) rawname, namelen -1)) { + return EINVAL; + } + + *_pvt = pvt; + *_rawname = rawname; + return EOK; +} + static void nss_cmd_getbynam_done(struct tevent_req *req) { struct nss_dom_ctx *dctx = tevent_req_callback_data(req, struct nss_dom_ctx); @@ -4255,6 +4571,11 @@ static int nss_cmd_getidbysid(struct cli_ctx *cctx) return nss_cmd_getbysid(SSS_NSS_GETIDBYSID, cctx); } +static int nss_cmd_get_user_attr(struct cli_ctx *cctx) +{ + return nss_cmd_getbynam(SSS_NSS_GET_USER_ATTR, cctx); +} + struct cli_protocol_version *register_cli_protocol_version(void) { static struct cli_protocol_version nss_cli_protocol_version[] = { @@ -4290,6 +4611,7 @@ static struct sss_cmd_table nss_cmds[] = { {SSS_NSS_GETSIDBYID, nss_cmd_getsidbyid}, {SSS_NSS_GETNAMEBYSID, nss_cmd_getnamebysid}, {SSS_NSS_GETIDBYSID, nss_cmd_getidbysid}, + {SSS_NSS_GET_USER_ATTR, nss_cmd_get_user_attr}, {SSS_CLI_NULL, NULL} }; diff --git a/src/responder/nss/nsssrv_private.h b/src/responder/nss/nsssrv_private.h index 04f1330c6..772e31d3e 100644 --- a/src/responder/nss/nsssrv_private.h +++ b/src/responder/nss/nsssrv_private.h @@ -67,6 +67,7 @@ struct nss_dom_ctx { /* For a case when we are discovering subdomains */ const char *rawname; + const uint8_t *pvt; bool check_provider; -- cgit