diff options
-rw-r--r-- | src/responder/common/responder.h | 11 | ||||
-rw-r--r-- | src/responder/nss/nsssrv_cmd.c | 995 |
2 files changed, 717 insertions, 289 deletions
diff --git a/src/responder/common/responder.h b/src/responder/common/responder.h index deb1e5a3c..0f59ffd43 100644 --- a/src/responder/common/responder.h +++ b/src/responder/common/responder.h @@ -92,6 +92,11 @@ struct resp_ctx { void *pvt_ctx; }; +/* Needed for the NSS responder */ +struct getent_ref_tracker { + void *pvt; +}; + struct cli_ctx { struct tevent_context *ev; struct resp_ctx *rctx; @@ -104,6 +109,12 @@ struct cli_ctx { int32_t client_euid; int32_t client_egid; int32_t client_pid; + + int pwent_dom_idx; + int pwent_cur; + + int grent_dom_idx; + int grent_cur; }; struct sss_cmd_table { diff --git a/src/responder/nss/nsssrv_cmd.c b/src/responder/nss/nsssrv_cmd.c index 719608c0c..6df705fb6 100644 --- a/src/responder/nss/nsssrv_cmd.c +++ b/src/responder/nss/nsssrv_cmd.c @@ -35,18 +35,20 @@ struct nss_cmd_ctx { bool check_next; bool enum_cached; + int saved_dom_idx; + int saved_cur; }; struct dom_ctx { struct sss_domain_info *domain; struct ldb_result *res; - int cur; }; struct getent_ctx { struct dom_ctx *doms; int num; - int cur; + bool ready; + struct setent_req_list *reqs; }; struct nss_dom_ctx { @@ -172,6 +174,63 @@ static int nss_cmd_done(struct nss_cmd_ctx *cmdctx, int ret) return EOK; } +/*************************** + * Enumeration procedures * + ***************************/ + +struct setent_req_list { + struct setent_req_list *prev; + struct setent_req_list *next; + struct getent_ctx *getent_ctx; + + struct tevent_req *req; +}; + +static int +setent_remove_ref(TALLOC_CTX *ctx); +static errno_t +setent_add_ref(TALLOC_CTX *memctx, + struct getent_ctx *getent_ctx, + struct tevent_req *req) +{ + struct setent_req_list *entry = + talloc_zero(memctx, struct setent_req_list); + if (!entry) { + return ENOMEM; + } + + entry->req = req; + entry->getent_ctx = getent_ctx; + DLIST_ADD_END(getent_ctx->reqs, entry, struct setent_req_list *); + + talloc_set_destructor((TALLOC_CTX *)entry, setent_remove_ref); + return EOK; +} + +static int +setent_remove_ref(TALLOC_CTX *ctx) +{ + struct setent_req_list *entry = + talloc_get_type(ctx, struct setent_req_list); + DLIST_REMOVE(entry->getent_ctx->reqs, entry); + return 0; +} + +struct setent_ctx { + struct cli_ctx *client; + struct nss_ctx *nctx; + struct nss_dom_ctx *dctx; + struct getent_ctx *getent_ctx; +}; + +struct setent_step_ctx { + struct nss_ctx *nctx; + struct nss_dom_ctx *dctx; + struct getent_ctx *getent_ctx; + struct resp_ctx *rctx; + bool enum_cached; +}; + /**************************************************************************** * PASSWD db related functions ***************************************************************************/ @@ -944,32 +1003,176 @@ done: * the last returned user. */ static int nss_cmd_getpwent_immediate(struct nss_cmd_ctx *cmdctx); +struct tevent_req * nss_cmd_setpwent_send(TALLOC_CTX *mem_ctx, + struct cli_ctx *client); +static void nss_cmd_setpwent_done(struct tevent_req *req); +static int nss_cmd_setpwent(struct cli_ctx *cctx) +{ + struct nss_cmd_ctx *cmdctx; + struct tevent_req *req; + errno_t ret = EOK; -static void nss_cmd_getpwent_dp_callback(uint16_t err_maj, uint32_t err_min, - const char *err_msg, void *ptr); + cmdctx = talloc_zero(cctx, struct nss_cmd_ctx); + if (!cmdctx) { + return ENOMEM; + } + cmdctx->cctx = cctx; + + req = nss_cmd_setpwent_send(cmdctx, cctx); + if (!req) { + DEBUG(0, ("Fatal error calling nss_cmd_setpwent_send\n")); + ret = EIO; + goto done; + } + tevent_req_set_callback(req, nss_cmd_setpwent_done, cmdctx); + +done: + return nss_cmd_done(cmdctx, ret); +} -static int nss_cmd_getpwent_search(struct nss_dom_ctx *dctx) +static errno_t nss_cmd_setpwent_step(struct setent_step_ctx *step_ctx); +struct tevent_req *nss_cmd_setpwent_send(TALLOC_CTX *mem_ctx, + struct cli_ctx *client) { - struct nss_cmd_ctx *cmdctx = dctx->cmdctx; - struct sss_domain_info *dom = dctx->domain; - struct cli_ctx *cctx = cmdctx->cctx; - struct sysdb_ctx *sysdb; - struct ldb_result *res; - struct getent_ctx *pctx; + errno_t ret; struct nss_ctx *nctx; - int timeout; - int ret; + struct tevent_req *req; + struct setent_ctx *state; + struct sss_domain_info *dom; + struct setent_step_ctx *step_ctx; + bool enum_cached = false; + time_t now = time(NULL); - nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); - pctx = nctx->pctx; - if (pctx == NULL) { - pctx = talloc_zero(nctx, struct getent_ctx); - if (!pctx) { - return ENOMEM; + DEBUG(4, ("Received setpwent request\n")); + nctx = talloc_get_type(client->rctx->pvt_ctx, struct nss_ctx); + + /* Reset the read pointers */ + client->pwent_dom_idx = 0; + client->pwent_cur = 0; + + req = tevent_req_create(mem_ctx, &state, struct setent_ctx); + if (!req) { + DEBUG(0, ("Could not create tevent request for setpwent\n")); + return NULL; + } + + state->nctx = nctx; + state->client = client; + + /* do not query backends if we have a recent enumeration */ + if (nctx->enum_cache_timeout) { + if (nctx->last_user_enum + nctx->enum_cache_timeout > now) { + enum_cached = true; + } + } + + state->dctx = talloc_zero(state, struct nss_dom_ctx); + if (!state->dctx) { + ret = ENOMEM; + goto error; + } + + /* check if enumeration is enabled in any domain */ + for (dom = client->rctx->domains; dom; dom = dom->next) { + if (dom->enumerate != 0) break; + } + state->dctx->domain = dom; + + if (state->dctx->domain == NULL) { + DEBUG(2, ("Enumeration disabled on all domains!\n")); + ret = ENOENT; + goto error; + } + + state->dctx->check_provider = + NEED_CHECK_PROVIDER(state->dctx->domain->provider); + + /* Is the result context already available */ + if (state->nctx->pctx) { + if (state->nctx->pctx->ready) { + /* All of the necessary data is in place + * We can return now, getpwent requests will work at this point + */ + tevent_req_done(req); + tevent_req_post(req, state->nctx->rctx->ev); + } + else { + /* Object is still being constructed + * Register for notification when it's + * ready. + */ + ret = setent_add_ref(state->client, state->nctx->pctx, req); + if (ret != EOK) { + talloc_free(req); + return NULL; + } } - nctx->pctx = pctx; + return req; } + /* Create a new result context + * We are creating it on the nss_ctx so that it doesn't + * go away if the original request does. We will delete + * it when the refcount goes to zero; + */ + state->nctx->pctx = talloc_zero(nctx, struct getent_ctx); + if (!state->nctx->pctx) { + ret = ENOMEM; + goto error; + } + state->getent_ctx = nctx->pctx; + + /* Add a callback reference for ourselves */ + setent_add_ref(state->client, state->nctx->pctx, req); + + /* ok, start the searches */ + step_ctx = talloc_zero(state->getent_ctx, struct setent_step_ctx); + if (!step_ctx) { + ret = ENOMEM; + goto error; + } + + /* Steal the dom_ctx onto the step_ctx so it doesn't go out of scope if + * this request is canceled while other requests are in-progress. + */ + step_ctx->dctx = talloc_steal(step_ctx, state->dctx); + step_ctx->nctx = state->nctx; + step_ctx->getent_ctx = state->getent_ctx; + step_ctx->rctx = client->rctx; + step_ctx->enum_cached = enum_cached; + + ret = nss_cmd_setpwent_step(step_ctx); + if (ret != EOK) goto error; + + return req; + + error: + tevent_req_error(req, ret); + tevent_req_post(req, client->rctx->ev); + return req; +} + +static void nss_cmd_setpwent_dp_callback(uint16_t err_maj, uint32_t err_min, + const char *err_msg, void *ptr); +static void setpwent_result_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *pvt); +static errno_t nss_cmd_setpwent_step(struct setent_step_ctx *step_ctx) +{ + errno_t ret; + struct sss_domain_info *dom = step_ctx->dctx->domain; + struct resp_ctx *rctx = step_ctx->rctx; + struct nss_dom_ctx *dctx = step_ctx->dctx; + struct getent_ctx *pctx = step_ctx->getent_ctx; + struct nss_ctx *nctx = step_ctx->nctx; + struct sysdb_ctx *sysdb; + struct ldb_result *res; + struct setent_req_list *req; + struct timeval tv; + struct tevent_timer *te; + int timeout; + while (dom) { while (dom && dom->enumerate == 0) { dom = dom->next; @@ -980,7 +1183,7 @@ static int nss_cmd_getpwent_search(struct nss_dom_ctx *dctx) if (dom != dctx->domain) { /* make sure we reset the check_provider flag when we check * a new domain */ - if (cmdctx->enum_cached) { + if (step_ctx->enum_cached) { dctx->check_provider = false; } else { dctx->check_provider = NEED_CHECK_PROVIDER(dom->provider); @@ -990,9 +1193,9 @@ static int nss_cmd_getpwent_search(struct nss_dom_ctx *dctx) /* make sure to update the dctx if we changed domain */ dctx->domain = dom; - DEBUG(4, ("Requesting info for domain [%s]\n", dom->name)); + DEBUG(6, ("Requesting info for domain [%s]\n", dom->name)); - ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, dom, &sysdb); + ret = sysdb_get_ctx_from_list(rctx->db_list, dom, &sysdb); if (ret != EOK) { DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); return EIO; @@ -1001,10 +1204,12 @@ static int nss_cmd_getpwent_search(struct nss_dom_ctx *dctx) /* 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) { + /* Only do this once per provider */ dctx->check_provider = false; timeout = SSS_CLI_SOCKET_TIMEOUT; - ret = sss_dp_send_acct_req(cctx->rctx, cmdctx, - nss_cmd_getpwent_dp_callback, dctx, + + ret = sss_dp_send_acct_req(rctx, step_ctx, + nss_cmd_setpwent_dp_callback, step_ctx, timeout, dom->name, true, SSS_DP_USER, NULL, 0); if (ret == EOK) { @@ -1030,7 +1235,7 @@ static int nss_cmd_getpwent_search(struct nss_dom_ctx *dctx) continue; } - pctx->doms = talloc_realloc(pctx, pctx->doms, + nctx->pctx->doms = talloc_realloc(pctx, pctx->doms, struct dom_ctx, pctx->num +1); if (!pctx->doms) { talloc_free(pctx); @@ -1038,41 +1243,65 @@ static int nss_cmd_getpwent_search(struct nss_dom_ctx *dctx) return ENOMEM; } - pctx->doms[pctx->num].domain = dctx->domain; - pctx->doms[pctx->num].res = talloc_steal(pctx->doms, res); - pctx->doms[pctx->num].cur = 0; + nctx->pctx->doms[pctx->num].domain = dctx->domain; + nctx->pctx->doms[pctx->num].res = talloc_steal(pctx->doms, res); - pctx->num++; + nctx->pctx->num++; /* do not reply until all domain searches are done */ dom = dom->next; } - /* set cache mark */ - nctx->last_user_enum = time(NULL); + /* We've finished all our lookups + * The result object is now safe to read. + */ + nctx->pctx->ready = true; - if (cmdctx->immediate) { - /* this was a getpwent call w/o setpwent, - * return immediately one result */ - return nss_cmd_getpwent_immediate(cmdctx); + /* Set up a lifetime timer for this result object + * We don't want this result object to outlive the + * enum cache refresh timeout + */ + tv = tevent_timeval_current_ofs(nctx->enum_cache_timeout, 0); + te = tevent_add_timer(rctx->ev, nctx->pctx, tv, + setpwent_result_timeout, nctx); + if (!te) { + DEBUG(0, ("Could not set up life timer for setpwent result object. " + "Entries may become stale.\n")); } - /* create response packet */ - ret = sss_packet_new(cctx->creq, 0, - sss_packet_get_cmd(cctx->creq->in), - &cctx->creq->out); - if (ret == EOK) { - sss_cmd_done(cctx, cmdctx); + /* Notify the waiting clients */ + while (nctx->pctx->reqs) { + tevent_req_done(nctx->pctx->reqs->req); + /* Freeing each entry in the list removes it from the dlist */ + req = nctx->pctx->reqs; + nctx->pctx->reqs = nctx->pctx->reqs->next; + talloc_free(req); } - return ret; + return EOK; } -static void nss_cmd_getpwent_dp_callback(uint16_t err_maj, uint32_t err_min, +static void setpwent_result_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *pvt) +{ + struct nss_ctx *nctx = talloc_get_type(pvt, struct nss_ctx); + + DEBUG(1, ("setpwent result object has expired. Cleaning up.\n")); + + /* Free the passwd enumeration context. + * If additional getpwent requests come in, they will invoke + * an implicit setpwent and refresh the result object. + */ + talloc_zfree(nctx->pctx); +} + +static void nss_cmd_setpwent_dp_callback(uint16_t err_maj, uint32_t err_min, const char *err_msg, void *ptr) { - struct nss_dom_ctx *dctx = talloc_get_type(ptr, struct nss_dom_ctx); - struct nss_cmd_ctx *cmdctx = dctx->cmdctx; - struct cli_ctx *cctx = cmdctx->cctx; + struct setent_step_ctx *step_ctx = + talloc_get_type(ptr, struct setent_step_ctx); + struct getent_ctx *pctx = step_ctx->nctx->pctx; int ret; if (err_maj) { @@ -1082,84 +1311,117 @@ static void nss_cmd_getpwent_dp_callback(uint16_t err_maj, uint32_t err_min, (unsigned int)err_maj, (unsigned int)err_min, err_msg)); } - ret = nss_cmd_getpwent_search(dctx); - + ret = nss_cmd_setpwent_step(step_ctx); if (ret) { - NSS_CMD_FATAL_ERROR(cctx); + /* Notify any waiting processes of failure */ + while(step_ctx->nctx->pctx->reqs) { + tevent_req_error(pctx->reqs->req, ret); + /* Freeing each entry in the list removes it from the dlist */ + talloc_free(pctx->reqs); + } } } -static int nss_cmd_setpwent_ext(struct cli_ctx *cctx, bool immediate) +static errno_t nss_cmd_setpwent_recv(struct tevent_req *req) { - struct sss_domain_info *dom; - struct nss_cmd_ctx *cmdctx; - struct nss_dom_ctx *dctx; - struct nss_ctx *nctx; - time_t now = time(NULL); - int ret; + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} - DEBUG(4, ("Requesting info for all users\n")); +static void nss_cmd_setpwent_done(struct tevent_req *req) +{ + errno_t ret; + struct nss_cmd_ctx *cmdctx = + tevent_req_callback_data(req, struct nss_cmd_ctx); - nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); - talloc_free(nctx->pctx); - nctx->pctx = NULL; + ret = nss_cmd_setpwent_recv(req); + talloc_zfree(req); + if (ret == EOK || ret == ENOENT) { + /* Either we succeeded or no domains were eligible */ + ret = sss_packet_new(cmdctx->cctx->creq, 0, + sss_packet_get_cmd(cmdctx->cctx->creq->in), + &cmdctx->cctx->creq->out); + if (ret == EOK) { + sss_cmd_done(cmdctx->cctx, cmdctx); + return; + } + } - cmdctx = talloc_zero(cctx, struct nss_cmd_ctx); + /* Something bad happened */ + nss_cmd_done(cmdctx, ret); +} + +static void nss_cmd_implicit_setpwent_done(struct tevent_req *req); +static int nss_cmd_getpwent(struct cli_ctx *cctx) +{ + struct nss_ctx *nctx; + struct nss_cmd_ctx *cmdctx; + struct tevent_req *req; + + DEBUG(4, ("Requesting info for all accounts\n")); + + cmdctx = talloc(cctx, struct nss_cmd_ctx); if (!cmdctx) { return ENOMEM; } cmdctx->cctx = cctx; - cmdctx->immediate = immediate; - dctx = talloc_zero(cmdctx, struct nss_dom_ctx); - if (!dctx) { - ret = ENOMEM; - goto done; - } - dctx->cmdctx = cmdctx; + /* Save the current index and cursor locations + * If we end up calling setpwent implicitly, because the response object + * expired and has to be recreated, we want to resume from the same + * location. + */ + cmdctx->saved_dom_idx = cctx->pwent_dom_idx; + cmdctx->saved_cur = cctx->pwent_cur; - /* do not query backends if we have a recent enumeration */ - if (nctx->enum_cache_timeout) { - if (nctx->last_user_enum + nctx->enum_cache_timeout > now) { - cmdctx->enum_cached = true; + nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); + if(!nctx->pctx || !nctx->pctx->ready) { + /* Make sure we invoke setpwent if it hasn't been run or is still + * processing from another client + */ + req = nss_cmd_setpwent_send(cctx, cctx); + if (!req) { + return EIO; } + tevent_req_set_callback(req, nss_cmd_implicit_setpwent_done, cmdctx); + return EOK; } - /* check if enumeration is enabled in any domain */ - for (dom = cctx->rctx->domains; dom; dom = dom->next) { - if (dom->enumerate != 0) break; + return nss_cmd_getpwent_immediate(cmdctx); +} + +static int nss_cmd_retpwent(struct cli_ctx *cctx, int num); +static int nss_cmd_getpwent_immediate(struct nss_cmd_ctx *cmdctx) +{ + struct cli_ctx *cctx = cmdctx->cctx; + uint8_t *body; + size_t blen; + uint32_t num; + int ret; + + /* get max num of entries to return in one call */ + sss_packet_get_body(cctx->creq->in, &body, &blen); + if (blen != sizeof(uint32_t)) { + return EINVAL; } - dctx->domain = dom; + num = *((uint32_t *)body); - if (dctx->domain == NULL) { - DEBUG(2, ("Enumeration disabled on all domains!\n")); - if (cmdctx->immediate) { - ret = ENOENT; - } else { - ret = sss_packet_new(cctx->creq, 0, - sss_packet_get_cmd(cctx->creq->in), - &cctx->creq->out); - if (ret == EOK) { - sss_cmd_done(cctx, cmdctx); - } - } - goto done; + /* create response packet */ + ret = sss_packet_new(cctx->creq, 0, + sss_packet_get_cmd(cctx->creq->in), + &cctx->creq->out); + if (ret != EOK) { + return ret; } - dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); + ret = nss_cmd_retpwent(cctx, num); - /* ok, start the searches */ - ret = nss_cmd_getpwent_search(dctx); -done: - return nss_cmd_done(cmdctx, ret); -} + sss_packet_set_error(cctx->creq->out, ret); + sss_cmd_done(cctx, cmdctx); -static int nss_cmd_setpwent(struct cli_ctx *cctx) -{ - return nss_cmd_setpwent_ext(cctx, false); + return EOK; } - static int nss_cmd_retpwent(struct cli_ctx *cctx, int num) { struct nss_ctx *nctx; @@ -1175,26 +1437,26 @@ static int nss_cmd_retpwent(struct cli_ctx *cctx, int num) pctx = nctx->pctx; while (ret == ENOENT) { - if (pctx->cur >= pctx->num) break; + if (cctx->pwent_dom_idx >= pctx->num) break; - pdom = &pctx->doms[pctx->cur]; + pdom = &pctx->doms[cctx->pwent_dom_idx]; - n = pdom->res->count - pdom->cur; - if (n == 0 && (pctx->cur+1 < pctx->num)) { - pctx->cur++; - pdom = &pctx->doms[pctx->cur]; - n = pdom->res->count - pdom->cur; + n = pdom->res->count - cctx->pwent_cur; + if (n <= 0 && (cctx->pwent_dom_idx+1 < pctx->num)) { + cctx->pwent_dom_idx++; + pdom = &pctx->doms[cctx->pwent_dom_idx]; + n = pdom->res->count - cctx->pwent_cur; } if (!n) break; if (n > num) n = num; - msgs = &(pdom->res->msgs[pdom->cur]); + msgs = &(pdom->res->msgs[cctx->pwent_cur]); ret = fill_pwent(cctx->creq->out, pdom->domain, nctx, true, msgs, &n); - pdom->cur += n; + cctx->pwent_cur += n; } none: @@ -1204,58 +1466,35 @@ none: return ret; } -static int nss_cmd_getpwent_immediate(struct nss_cmd_ctx *cmdctx) +static void nss_cmd_implicit_setpwent_done(struct tevent_req *req) { - struct cli_ctx *cctx = cmdctx->cctx; - uint8_t *body; - size_t blen; - uint32_t num; - int ret; + errno_t ret; + struct nss_cmd_ctx *cmdctx = + tevent_req_callback_data(req, struct nss_cmd_ctx); - /* get max num of entries to return in one call */ - sss_packet_get_body(cctx->creq->in, &body, &blen); - if (blen != sizeof(uint32_t)) { - return EINVAL; - } - num = *((uint32_t *)body); + ret = nss_cmd_setpwent_recv(req); + talloc_zfree(req); - /* create response packet */ - ret = sss_packet_new(cctx->creq, 0, - sss_packet_get_cmd(cctx->creq->in), - &cctx->creq->out); - if (ret != EOK) { - return ret; + /* ENOENT is acceptable, as it just means that there were no entries + * to be returned. This will be handled gracefully in nss_cmd_retpwent + * later. + */ + if (ret != EOK && ret != ENOENT) { + DEBUG(0, ("Implicit setpwent failed with unexpected error [%d][%s]\n", + ret, strerror(ret))); + NSS_CMD_FATAL_ERROR(cmdctx); } - ret = nss_cmd_retpwent(cctx, num); - - sss_packet_set_error(cctx->creq->out, ret); - sss_cmd_done(cctx, cmdctx); + /* Restore the saved index and cursor locations */ + cmdctx->cctx->pwent_dom_idx = cmdctx->saved_dom_idx; + cmdctx->cctx->pwent_cur = cmdctx->saved_cur; - return EOK; -} - -static int nss_cmd_getpwent(struct cli_ctx *cctx) -{ - struct nss_ctx *nctx; - struct nss_cmd_ctx *cmdctx; - - DEBUG(4, ("Requesting info for all accounts\n")); - - nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); - - /* see if we need to trigger an implicit setpwent() */ - if (nctx->pctx == NULL) { - return nss_cmd_setpwent_ext(cctx, true); - } - - cmdctx = talloc(cctx, struct nss_cmd_ctx); - if (!cmdctx) { - return ENOMEM; + ret = nss_cmd_getpwent_immediate(cmdctx); + if (ret != EOK) { + DEBUG(0, ("Immediate retrieval failed with unexpected error " + "[%d][%s]\n", ret, strerror(ret))); + NSS_CMD_FATAL_ERROR(cmdctx); } - cmdctx->cctx = cctx; - - return nss_cmd_getpwent_immediate(cmdctx); } static int nss_cmd_endpwent(struct cli_ctx *cctx) @@ -1277,9 +1516,9 @@ static int nss_cmd_endpwent(struct cli_ctx *cctx) } if (nctx->pctx == NULL) goto done; - /* free results and reset */ - talloc_free(nctx->pctx); - nctx->pctx = NULL; + /* Reset the indices so that subsequent requests start at zero */ + cctx->pwent_dom_idx = 0; + cctx->pwent_cur = 0; done: sss_cmd_done(cctx, NULL); @@ -2014,33 +2253,176 @@ done: * even if the data is still being fetched * - make getgrent() wait on the mutex */ -static int nss_cmd_getgrent_immediate(struct nss_cmd_ctx *cmdctx); +struct tevent_req *nss_cmd_setgrent_send(TALLOC_CTX *mem_ctx, + struct cli_ctx *client); +static void nss_cmd_setgrent_done(struct tevent_req *req); +static int nss_cmd_setgrent(struct cli_ctx *cctx) +{ + struct nss_cmd_ctx *cmdctx; + struct tevent_req *req; + errno_t ret = EOK; -static void nss_cmd_getgrent_dp_callback(uint16_t err_maj, uint32_t err_min, - const char *err_msg, void *ptr); + cmdctx = talloc_zero(cctx, struct nss_cmd_ctx); + if (!cmdctx) { + return ENOMEM; + } + cmdctx->cctx = cctx; + + req = nss_cmd_setgrent_send(cmdctx, cctx); + if (!req) { + DEBUG(0, ("Fatal error calling nss_cmd_setgrent_send\n")); + ret = EIO; + goto done; + } + tevent_req_set_callback(req, nss_cmd_setgrent_done, cmdctx); + +done: + return nss_cmd_done(cmdctx, ret); +} -static int nss_cmd_getgrent_search(struct nss_dom_ctx *dctx) +static errno_t nss_cmd_setgrent_step(struct setent_step_ctx *step_ctx); +struct tevent_req *nss_cmd_setgrent_send(TALLOC_CTX *mem_ctx, + struct cli_ctx *client) { - struct nss_cmd_ctx *cmdctx = dctx->cmdctx; - struct sss_domain_info *dom = dctx->domain; - struct cli_ctx *cctx = cmdctx->cctx; - struct sysdb_ctx *sysdb; - struct ldb_result *res; - struct getent_ctx *gctx; + errno_t ret; struct nss_ctx *nctx; - int timeout; - int ret; + struct tevent_req *req; + struct setent_ctx *state; + struct sss_domain_info *dom; + struct setent_step_ctx *step_ctx; + bool enum_cached = false; + time_t now = time(NULL); - nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); - gctx = nctx->gctx; - if (gctx == NULL) { - gctx = talloc_zero(nctx, struct getent_ctx); - if (!gctx) { - return ENOMEM; + DEBUG(4, ("Received setgrent request\n")); + nctx = talloc_get_type(client->rctx->pvt_ctx, struct nss_ctx); + + /* Reset the read pointers */ + client->grent_dom_idx = 0; + client->grent_cur = 0; + + req = tevent_req_create(mem_ctx, &state, struct setent_ctx); + if (!req) { + DEBUG(0, ("Could not create tevent request for setgrent\n")); + return NULL; + } + + state->nctx = nctx; + state->client = client; + + /* do not query backends if we have a recent enumeration */ + if (nctx->enum_cache_timeout) { + if (nctx->last_user_enum + nctx->enum_cache_timeout > now) { + enum_cached = true; } - nctx->gctx = gctx; } + state->dctx = talloc_zero(state, struct nss_dom_ctx); + if (!state->dctx) { + ret = ENOMEM; + goto error; + } + + /* check if enumeration is enabled in any domain */ + for (dom = client->rctx->domains; dom; dom = dom->next) { + if (dom->enumerate != 0) break; + } + state->dctx->domain = dom; + + if (state->dctx->domain == NULL) { + DEBUG(2, ("Enumeration disabled on all domains!\n")); + ret = ENOENT; + goto error; + } + + state->dctx->check_provider = + NEED_CHECK_PROVIDER(state->dctx->domain->provider); + + /* Is the result context already available */ + if (state->nctx->gctx) { + if (state->nctx->gctx->ready) { + /* All of the necessary data is in place + * We can return now, getgrent requests will work at this point + */ + tevent_req_done(req); + tevent_req_post(req, state->nctx->rctx->ev); + } + else { + /* Object is still being constructed + * Register for notification when it's + * ready. + */ + ret = setent_add_ref(state->client, state->nctx->gctx, req); + if (ret != EOK) { + talloc_free(req); + return NULL; + } + } + return req; + } + + /* Create a new result context + * We are creating it on the nss_ctx so that it doesn't + * go away if the original request does. We will delete + * it when the refcount goes to zero; + */ + state->nctx->gctx = talloc_zero(nctx, struct getent_ctx); + if (!state->nctx->gctx) { + ret = ENOMEM; + goto error; + } + state->getent_ctx = nctx->gctx; + + /* Add a callback reference for ourselves */ + setent_add_ref(state->client, state->nctx->gctx, req); + + /* ok, start the searches */ + step_ctx = talloc_zero(state->getent_ctx, struct setent_step_ctx); + if (!step_ctx) { + ret = ENOMEM; + goto error; + } + + /* Steal the dom_ctx onto the step_ctx so it doesn't go out of scope if + * this request is canceled while other requests are in-progress. + */ + step_ctx->dctx = talloc_steal(step_ctx, state->dctx); + step_ctx->nctx = state->nctx; + step_ctx->getent_ctx = state->getent_ctx; + step_ctx->rctx = client->rctx; + step_ctx->enum_cached = enum_cached; + + ret = nss_cmd_setgrent_step(step_ctx); + if (ret != EOK) goto error; + + return req; + + error: + tevent_req_error(req, ret); + tevent_req_post(req, client->rctx->ev); + return req; +} + +static void nss_cmd_setgrent_dp_callback(uint16_t err_maj, uint32_t err_min, + const char *err_msg, void *ptr); +static void setgrent_result_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *pvt); +static errno_t nss_cmd_setgrent_step(struct setent_step_ctx *step_ctx) +{ + errno_t ret; + struct sss_domain_info *dom = step_ctx->dctx->domain; + struct resp_ctx *rctx = step_ctx->rctx; + struct nss_dom_ctx *dctx = step_ctx->dctx; + struct getent_ctx *gctx = step_ctx->getent_ctx; + struct nss_ctx *nctx = step_ctx->nctx; + struct sysdb_ctx *sysdb; + struct ldb_result *res; + struct setent_req_list *req; + struct timeval tv; + struct tevent_timer *te; + int timeout; + while (dom) { while (dom && dom->enumerate == 0) { dom = dom->next; @@ -2051,7 +2433,7 @@ static int nss_cmd_getgrent_search(struct nss_dom_ctx *dctx) if (dom != dctx->domain) { /* make sure we reset the check_provider flag when we check * a new domain */ - if (cmdctx->enum_cached) { + if (step_ctx->enum_cached) { dctx->check_provider = false; } else { dctx->check_provider = NEED_CHECK_PROVIDER(dom->provider); @@ -2061,9 +2443,9 @@ static int nss_cmd_getgrent_search(struct nss_dom_ctx *dctx) /* make sure to update the dctx if we changed domain */ dctx->domain = dom; - DEBUG(4, ("Requesting info for domain [%s]\n", dom->name)); + DEBUG(6, ("Requesting info for domain [%s]\n", dom->name)); - ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, dom, &sysdb); + ret = sysdb_get_ctx_from_list(rctx->db_list, dom, &sysdb); if (ret != EOK) { DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); return EIO; @@ -2072,10 +2454,12 @@ static int nss_cmd_getgrent_search(struct nss_dom_ctx *dctx) /* 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) { + /* Only do this once per provider */ dctx->check_provider = false; timeout = SSS_CLI_SOCKET_TIMEOUT; - ret = sss_dp_send_acct_req(cctx->rctx, cmdctx, - nss_cmd_getgrent_dp_callback, dctx, + + ret = sss_dp_send_acct_req(rctx, step_ctx, + nss_cmd_setgrent_dp_callback, step_ctx, timeout, dom->name, true, SSS_DP_GROUP, NULL, 0); if (ret == EOK) { @@ -2101,8 +2485,7 @@ static int nss_cmd_getgrent_search(struct nss_dom_ctx *dctx) continue; } - - gctx->doms = talloc_realloc(gctx, gctx->doms, + nctx->gctx->doms = talloc_realloc(gctx, gctx->doms, struct dom_ctx, gctx->num +1); if (!gctx->doms) { talloc_free(gctx); @@ -2110,41 +2493,65 @@ static int nss_cmd_getgrent_search(struct nss_dom_ctx *dctx) return ENOMEM; } - gctx->doms[gctx->num].domain = dctx->domain; - gctx->doms[gctx->num].res = talloc_steal(gctx->doms, res); - gctx->doms[gctx->num].cur = 0; + nctx->gctx->doms[gctx->num].domain = dctx->domain; + nctx->gctx->doms[gctx->num].res = talloc_steal(gctx->doms, res); - gctx->num++; + nctx->gctx->num++; /* do not reply until all domain searches are done */ dom = dom->next; } - /* set cache mark */ - nctx->last_group_enum = time(NULL); + /* We've finished all our lookups + * The result object is now safe to read. + */ + nctx->gctx->ready = true; - if (cmdctx->immediate) { - /* this was a getgrent call w/o setgrent, - * return immediately one result */ - return nss_cmd_getgrent_immediate(cmdctx); + /* Set up a lifetime timer for this result object + * We don't want this result object to outlive the + * enum cache refresh timeout + */ + tv = tevent_timeval_current_ofs(nctx->enum_cache_timeout, 0); + te = tevent_add_timer(rctx->ev, nctx->gctx, tv, + setgrent_result_timeout, nctx); + if (!te) { + DEBUG(0, ("Could not set up life timer for setgrent result object. " + "Entries may become stale.\n")); } - /* create response packet */ - ret = sss_packet_new(cctx->creq, 0, - sss_packet_get_cmd(cctx->creq->in), - &cctx->creq->out); - if (ret == EOK) { - sss_cmd_done(cctx, cmdctx); + /* Notify the waiting clients */ + while (nctx->gctx->reqs) { + tevent_req_done(nctx->gctx->reqs->req); + /* Freeing each entry in the list removes it from the dlist */ + req = nctx->gctx->reqs; + nctx->gctx->reqs = nctx->gctx->reqs->next; + talloc_free(req); } - return ret; + return EOK; } -static void nss_cmd_getgrent_dp_callback(uint16_t err_maj, uint32_t err_min, +static void setgrent_result_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *pvt) +{ + struct nss_ctx *nctx = talloc_get_type(pvt, struct nss_ctx); + + DEBUG(1, ("setgrent result object has expired. Cleaning up.\n")); + + /* Free the group enumeration context. + * If additional getgrent requests come in, they will invoke + * an implicit setgrent and refresh the result object. + */ + talloc_zfree(nctx->gctx); +} + +static void nss_cmd_setgrent_dp_callback(uint16_t err_maj, uint32_t err_min, const char *err_msg, void *ptr) { - struct nss_dom_ctx *dctx = talloc_get_type(ptr, struct nss_dom_ctx); - struct nss_cmd_ctx *cmdctx = dctx->cmdctx; - struct cli_ctx *cctx = cmdctx->cctx; + struct setent_step_ctx *step_ctx = + talloc_get_type(ptr, struct setent_step_ctx); + struct getent_ctx *gctx = step_ctx->nctx->gctx; int ret; if (err_maj) { @@ -2154,81 +2561,44 @@ static void nss_cmd_getgrent_dp_callback(uint16_t err_maj, uint32_t err_min, (unsigned int)err_maj, (unsigned int)err_min, err_msg)); } - ret = nss_cmd_getgrent_search(dctx); - + ret = nss_cmd_setgrent_step(step_ctx); if (ret) { - NSS_CMD_FATAL_ERROR(cctx); + /* Notify any waiting processes of failure */ + while(step_ctx->nctx->gctx->reqs) { + tevent_req_error(gctx->reqs->req, ret); + /* Freeing each entry in the list removes it from the dlist */ + talloc_free(gctx->reqs); + } } } -static int nss_cmd_setgrent_ext(struct cli_ctx *cctx, bool immediate) +static errno_t nss_cmd_setgrent_recv(struct tevent_req *req) { - struct sss_domain_info *dom; - struct nss_cmd_ctx *cmdctx; - struct nss_dom_ctx *dctx; - struct nss_ctx *nctx; - time_t now = time(NULL); - int ret; - - DEBUG(4, ("Requesting info for all groups\n")); - - nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); - talloc_free(nctx->gctx); - nctx->gctx = NULL; - - cmdctx = talloc_zero(cctx, struct nss_cmd_ctx); - if (!cmdctx) { - return ENOMEM; - } - cmdctx->cctx = cctx; - cmdctx->immediate = immediate; - - dctx = talloc_zero(cmdctx, struct nss_dom_ctx); - if (!dctx) { - ret = ENOMEM; - goto done; - } - dctx->cmdctx = cmdctx; - - /* do not query backends if we have a recent enumeration */ - if (nctx->enum_cache_timeout) { - if (nctx->last_group_enum + nctx->enum_cache_timeout > now) { - cmdctx->enum_cached = true; - } - } + TEVENT_REQ_RETURN_ON_ERROR(req); + return EOK; +} - /* check if enumeration is enabled in any domain */ - for (dom = cctx->rctx->domains; dom; dom = dom->next) { - if (dom->enumerate != 0) break; - } - dctx->domain = dom; +static void nss_cmd_setgrent_done(struct tevent_req *req) +{ + errno_t ret; + struct nss_cmd_ctx *cmdctx = + tevent_req_callback_data(req, struct nss_cmd_ctx); - if (dctx->domain == NULL) { - DEBUG(2, ("Enumeration disabled on all domains!\n")); - if (cmdctx->immediate) { - ret = ENOENT; - } else { - ret = sss_packet_new(cctx->creq, 0, - sss_packet_get_cmd(cctx->creq->in), - &cctx->creq->out); - if (ret == EOK) { - sss_cmd_done(cctx, cmdctx); - } + ret = nss_cmd_setgrent_recv(req); + talloc_zfree(req); + if (ret == EOK || ret == ENOENT) { + /* Either we succeeded or no domains were eligible */ + ret = sss_packet_new(cmdctx->cctx->creq, 0, + sss_packet_get_cmd(cmdctx->cctx->creq->in), + &cmdctx->cctx->creq->out); + if (ret == EOK) { + sss_cmd_done(cmdctx->cctx, cmdctx); + return; } - goto done; } - dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider); - - /* ok, start the searches */ - ret = nss_cmd_getgrent_search(dctx); -done: - return nss_cmd_done(cmdctx, ret); -} - -static int nss_cmd_setgrent(struct cli_ctx *cctx) -{ - return nss_cmd_setgrent_ext(cctx, false); + /* Something bad happened */ + nss_cmd_done(cmdctx, ret); } static int nss_cmd_retgrent(struct cli_ctx *cctx, int num) @@ -2246,28 +2616,28 @@ static int nss_cmd_retgrent(struct cli_ctx *cctx, int num) gctx = nctx->gctx; while (ret == ENOENT) { - if (gctx->cur >= gctx->num) break; + if (cctx->grent_dom_idx >= gctx->num) break; - gdom = &gctx->doms[gctx->cur]; + gdom = &gctx->doms[cctx->grent_dom_idx]; - n = gdom->res->count - gdom->cur; - if (n == 0 && (gctx->cur+1 < gctx->num)) { - gctx->cur++; - gdom = &gctx->doms[gctx->cur]; - n = gdom->res->count - gdom->cur; + n = gdom->res->count - cctx->grent_cur; + if (n <= 0 && (cctx->grent_cur+1 < gctx->num)) { + cctx->grent_dom_idx++; + gdom = &gctx->doms[cctx->grent_dom_idx]; + n = gdom->res->count - cctx->grent_cur; } if (!n) break; if (n > num) n = num; - msgs = &(gdom->res->msgs[gdom->cur]); + msgs = &(gdom->res->msgs[cctx->grent_cur]); ret = fill_grent(cctx->creq->out, gdom->domain, nctx, true, msgs, &n); - gdom->cur += n; + cctx->grent_cur += n; } none: @@ -2308,29 +2678,77 @@ static int nss_cmd_getgrent_immediate(struct nss_cmd_ctx *cmdctx) return EOK; } +static void nss_cmd_implicit_setgrent_done(struct tevent_req *req); static int nss_cmd_getgrent(struct cli_ctx *cctx) { struct nss_ctx *nctx; struct nss_cmd_ctx *cmdctx; + struct tevent_req *req; DEBUG(4, ("Requesting info for all groups\n")); - nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); - - /* see if we need to trigger an implicit setpwent() */ - if (nctx->gctx == NULL) { - return nss_cmd_setgrent_ext(cctx, true); - } - cmdctx = talloc(cctx, struct nss_cmd_ctx); if (!cmdctx) { return ENOMEM; } cmdctx->cctx = cctx; + /* Save the current index and cursor locations + * If we end up calling setgrent implicitly, because the response object + * expired and has to be recreated, we want to resume from the same + * location. + */ + cmdctx->saved_dom_idx = cctx->grent_dom_idx; + cmdctx->saved_cur = cctx->grent_cur; + + nctx = talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx); + if(!nctx->gctx || !nctx->gctx->ready) { + /* Make sure we invoke setgrent if it hasn't been run or is still + * processing from another client + */ + req = nss_cmd_setgrent_send(cctx, cctx); + if (!req) { + return EIO; + } + tevent_req_set_callback(req, nss_cmd_implicit_setgrent_done, cmdctx); + return EOK; + } + return nss_cmd_getgrent_immediate(cmdctx); } +static errno_t nss_cmd_setgrent_recv(struct tevent_req *req); +static void nss_cmd_implicit_setgrent_done(struct tevent_req *req) +{ + errno_t ret; + struct nss_cmd_ctx *cmdctx = + tevent_req_callback_data(req, struct nss_cmd_ctx); + + ret = nss_cmd_setgrent_recv(req); + talloc_zfree(req); + + /* ENOENT is acceptable, as it just means that there were no entries + * to be returned. This will be handled gracefully in nss_cmd_retpwent + * later. + */ + if (ret != EOK && ret != ENOENT) { + DEBUG(0, ("Implicit setgrent failed with unexpected error [%d][%s]\n", + ret, strerror(ret))); + NSS_CMD_FATAL_ERROR(cmdctx); + } + + /* Restore the saved index and cursor locations */ + cmdctx->cctx->grent_dom_idx = cmdctx->saved_dom_idx; + cmdctx->cctx->grent_cur = cmdctx->saved_cur; + + ret = nss_cmd_getgrent_immediate(cmdctx); + if (ret != EOK) { + DEBUG(0, ("Immediate retrieval failed with unexpected error " + "[%d][%s]\n", ret, strerror(ret))); + NSS_CMD_FATAL_ERROR(cmdctx); + } +} + static int nss_cmd_endgrent(struct cli_ctx *cctx) { struct nss_ctx *nctx; @@ -2350,9 +2768,9 @@ static int nss_cmd_endgrent(struct cli_ctx *cctx) } if (nctx->gctx == NULL) goto done; - /* free results and reset */ - talloc_free(nctx->gctx); - nctx->gctx = NULL; + /* Reset the indices so that subsequent requests start at zero */ + cctx->grent_dom_idx = 0; + cctx->grent_cur = 0; done: sss_cmd_done(cctx, NULL); @@ -2674,4 +3092,3 @@ int nss_cmd_execute(struct cli_ctx *cctx) return EINVAL; } - |