summaryrefslogtreecommitdiffstats
path: root/server/responder/pam/pamsrv_cmd.c
diff options
context:
space:
mode:
Diffstat (limited to 'server/responder/pam/pamsrv_cmd.c')
-rw-r--r--server/responder/pam/pamsrv_cmd.c335
1 files changed, 316 insertions, 19 deletions
diff --git a/server/responder/pam/pamsrv_cmd.c b/server/responder/pam/pamsrv_cmd.c
index 2190b5668..e229862c0 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) {