diff options
Diffstat (limited to 'server/responder/pam')
-rw-r--r-- | server/responder/pam/pamsrv.h | 3 | ||||
-rw-r--r-- | server/responder/pam/pamsrv_cmd.c | 335 |
2 files changed, 319 insertions, 19 deletions
diff --git a/server/responder/pam/pamsrv.h b/server/responder/pam/pamsrv.h index c751ceed..f33c5dc7 100644 --- a/server/responder/pam/pamsrv.h +++ b/server/responder/pam/pamsrv.h @@ -19,6 +19,9 @@ struct pam_auth_req { struct pam_data *pd; pam_dp_callback_t *callback; + + bool check_provider; + void *data; }; struct sbus_method *register_pam_dp_methods(void); diff --git a/server/responder/pam/pamsrv_cmd.c b/server/responder/pam/pamsrv_cmd.c index 2190b566..e229862c 100644 --- a/server/responder/pam/pamsrv_cmd.c +++ b/server/responder/pam/pamsrv_cmd.c @@ -20,14 +20,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include <errno.h> -#include <talloc.h> - +#include <time.h> #include "util/util.h" #include "confdb/confdb.h" #include "responder/common/responder_packet.h" +#include "responder/common/responder.h" #include "providers/data_provider.h" #include "responder/pam/pamsrv.h" +#include "db/sysdb.h" static int pam_parse_in_data(struct sss_names_ctx *snctx, struct pam_data *pd, @@ -252,14 +252,25 @@ done: sss_cmd_done(cctx, preq); } +static void pam_check_user_dp_callback(uint16_t err_maj, uint32_t err_min, + const char *err_msg, void *ptr); +static void pam_check_user_callback(void *ptr, int status, + struct ldb_result *res); +static void pam_dom_forwarder(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 int pam_forwarder(struct cli_ctx *cctx, int pam_cmd) { struct sss_domain_info *dom; + struct pam_auth_req *preq; + struct pam_data *pd; uint8_t *body; size_t blen; + int timeout; int ret; - struct pam_auth_req *preq; - struct pam_data *pd; preq = talloc_zero(cctx, struct pam_auth_req); if (!preq) { @@ -279,42 +290,328 @@ static int pam_forwarder(struct cli_ctx *cctx, int pam_cmd) ((uint32_t *)(&body[blen - sizeof(uint32_t)]))[0] != END_OF_PAM_REQUEST) { DEBUG(1, ("Received data not terminated.\n")); talloc_free(preq); - return EINVAL; + ret = EINVAL; + goto done; } pd->cmd = pam_cmd; ret = pam_parse_in_data(cctx->rctx->names, pd, body, blen); - if (ret != 0) { + if (ret != EOK) { talloc_free(preq); - return EINVAL; + ret = EINVAL; + goto done; } + /* now check user is valid */ if (pd->domain) { for (dom = cctx->rctx->domains; dom; dom = dom->next) { if (strcasecmp(dom->name, pd->domain) == 0) break; } if (!dom) { talloc_free(preq); - return EINVAL; + ret = ENOENT; + goto done; } preq->domain = dom; } else { - DEBUG(4, ("Domain not provided, using default.\n")); - preq->domain = cctx->rctx->domains; - pd->domain = preq->domain->name; + for (dom = preq->cctx->rctx->domains; dom; dom = dom->next) { + if (dom->fqnames) continue; + +/* FIXME: need to support negative cache */ +#if HAVE_NEG_CACHE + ncret = sss_ncache_check_user(nctx->ncache, nctx->neg_timeout, + dom->name, cmdctx->name); + if (ncret == ENOENT) break; +#endif + break; + } + if (!dom) { + ret = ENOENT; + goto done; + } + preq->domain = dom; + } + + /* When auth is requested always search the provider first, + * do not rely on cached data unless the provider is completely + * offline */ + if (preq->domain->provider && + (pam_cmd == SSS_PAM_AUTHENTICATE || pam_cmd == SSS_PAM_SETCRED)) { + + /* no need to re-check later on */ + preq->check_provider = false; + timeout = SSS_CLI_SOCKET_TIMEOUT/2; + + ret = nss_dp_send_acct_req(preq->cctx->rctx, preq, + pam_check_user_dp_callback, preq, + timeout, preq->domain->name, NSS_DP_USER, + preq->pd->user, 0); + } + else { + preq->check_provider = (preq->domain->provider != NULL); + + ret = sysdb_getpwnam(preq, cctx->rctx->sysdb, + preq->domain, preq->pd->user, + pam_check_user_callback, preq); + } + +done: + if (ret != EOK) { + switch (ret) { + case ENOENT: + pd->pam_status = PAM_USER_UNKNOWN; + default: + pd->pam_status = PAM_SYSTEM_ERR; + } + pam_reply(preq); + } + return EOK; +} + +static void pam_check_user_dp_callback(uint16_t err_maj, uint32_t err_min, + const char *err_msg, void *ptr) +{ + struct pam_auth_req *preq = talloc_get_type(ptr, struct pam_auth_req); + struct ldb_result *res = NULL; + int ret; + + if ((err_maj != DP_ERR_OK) && (err_maj != DP_ERR_OFFLINE)) { + DEBUG(2, ("Unable to get information from Data Provider\n" + "Error: %u, %u, %s\n", + (unsigned int)err_maj, (unsigned int)err_min, err_msg)); + ret = EFAULT; + goto done; + } + + if (err_maj == DP_ERR_OFFLINE) { + if (preq->data) res = talloc_get_type(preq->data, struct ldb_result); + if (!res) res = talloc_zero(preq, struct ldb_result); + if (!res) { + ret = EFAULT; + goto done; + } + + pam_check_user_callback(preq, LDB_SUCCESS, res); + return; + } + + ret = sysdb_getpwnam(preq, preq->cctx->rctx->sysdb, + preq->domain, preq->pd->user, + pam_check_user_callback, preq); + +done: + if (ret != EOK) { + preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(preq); + } +} + +static void pam_check_user_callback(void *ptr, int status, + struct ldb_result *res) +{ + struct pam_auth_req *preq = talloc_get_type(ptr, struct pam_auth_req); + struct sss_domain_info *dom; + uint64_t lastUpdate; + bool call_provider = false; + int timeout; + int ret; + + if (status != LDB_SUCCESS) { + preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(preq); + return; + } + + timeout = SSS_CLI_SOCKET_TIMEOUT/2; + + if (preq->check_provider) { + switch (res->count) { + case 0: + call_provider = true; + break; + + case 1: + timeout = 30; /* FIXME: read from conf */ + + lastUpdate = ldb_msg_find_attr_as_uint64(res->msgs[0], + SYSDB_LAST_UPDATE, 0); + if (lastUpdate + timeout < time(NULL)) { + call_provider = true; + } + break; + + default: + DEBUG(1, ("check user call returned more than one result !?!\n")); + preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(preq); + return; + } + } + + if (call_provider) { + + /* dont loop forever :-) */ + preq->check_provider = false; + + /* keep around current data in case backend is offline */ + if (res->count) { + preq->data = talloc_steal(preq, res); + } + + ret = nss_dp_send_acct_req(preq->cctx->rctx, preq, + pam_check_user_dp_callback, preq, + timeout, preq->domain->name, NSS_DP_USER, + preq->pd->user, 0); + if (ret != EOK) { + DEBUG(3, ("Failed to dispatch request: %d(%s)\n", + ret, strerror(ret))); + preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(preq); + } + return; + } + + switch (res->count) { + case 0: + if (!preq->pd->domain) { + /* search next as the domain was unknown */ + + ret = EOK; + + /* skip domains that require FQnames or have negative caches */ + for (dom = preq->domain->next; dom; dom = dom->next) { + + if (dom->fqnames) continue; + +#if HAVE_NEG_CACHE + ncret = nss_ncache_check_user(nctx->ncache, + nctx->neg_timeout, + dom->name, cmdctx->name); + if (ncret == ENOENT) break; + + neghit = true; +#endif + break; + } +#if HAVE_NEG_CACHE + /* reset neghit if we still have a domain to check */ + if (dom) neghit = false; + + if (neghit) { + DEBUG(2, ("User [%s] does not exist! (negative cache)\n", + cmdctx->name)); + ret = ENOENT; + } +#endif + if (dom == NULL) { + DEBUG(2, ("No matching domain found for [%s], fail!\n", + preq->pd->user)); + ret = ENOENT; + } + + if (ret == EOK) { + preq->domain = dom; + preq->data = NULL; + + DEBUG(4, ("Requesting info for [%s@%s]\n", + preq->pd->user, preq->domain->name)); + + /* When auth is requested always search the provider first, + * do not rely on cached data unless the provider is + * completely offline */ + if (preq->domain->provider && + (preq->pd->cmd == SSS_PAM_AUTHENTICATE || + preq->pd->cmd == SSS_PAM_SETCRED)) { + + /* no need to re-check later on */ + preq->check_provider = false; + + ret = nss_dp_send_acct_req(preq->cctx->rctx, preq, + pam_check_user_dp_callback, + preq, timeout, + preq->domain->name, + NSS_DP_USER, + preq->pd->user, 0); + } + else { + preq->check_provider = (preq->domain->provider != NULL); + + ret = sysdb_getpwnam(preq, preq->cctx->rctx->sysdb, + preq->domain, preq->pd->user, + pam_check_user_callback, preq); + } + if (ret != EOK) { + DEBUG(1, ("Failed to make request to our cache!\n")); + } + } + + /* we made another call, end here */ + if (ret == EOK) return; + } + else { + ret = ENOENT; + } + + DEBUG(2, ("No results for check user call\n")); + +#if HAVE_NEG_CACHE + /* set negative cache only if not result of cache check */ + if (!neghit) { + ret = nss_ncache_set_user(nctx->ncache, false, + dctx->domain->name, cmdctx->name); + if (ret != EOK) { + NSS_CMD_FATAL_ERROR(cctx); + } + } +#endif + + if (ret != EOK) { + if (ret == ENOENT) { + preq->pd->pam_status = PAM_USER_UNKNOWN; + } else { + preq->pd->pam_status = PAM_SYSTEM_ERR; + } + pam_reply(preq); + return; + } + break; + + case 1: + + /* BINGO */ + pam_dom_forwarder(preq); + return; + + default: + DEBUG(1, ("check user call returned more than one result !?!\n")); + preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(preq); + } +} + +static void pam_dom_forwarder(struct pam_auth_req *preq) +{ + int ret; + + if (!preq->pd->domain) { + preq->pd->domain = preq->domain->name; } if (!preq->domain->provider) { preq->callback = pam_reply; - return LOCAL_pam_handler(preq); - }; - - preq->callback = pam_reply; - ret = pam_dp_send_req(preq, PAM_DP_TIMEOUT); - DEBUG(4, ("pam_dp_send_req returned %d\n", ret)); + ret = LOCAL_pam_handler(preq); + } + else { + preq->callback = pam_reply; + ret = pam_dp_send_req(preq, PAM_DP_TIMEOUT); + DEBUG(4, ("pam_dp_send_req returned %d\n", ret)); + } - return ret; + if (ret != EOK) { + preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(preq); + } } static int pam_cmd_authenticate(struct cli_ctx *cctx) { |