diff options
Diffstat (limited to 'src/responder/pam/pam_LOCAL_domain.c')
-rw-r--r-- | src/responder/pam/pam_LOCAL_domain.c | 476 |
1 files changed, 476 insertions, 0 deletions
diff --git a/src/responder/pam/pam_LOCAL_domain.c b/src/responder/pam/pam_LOCAL_domain.c new file mode 100644 index 00000000..34f0c8dd --- /dev/null +++ b/src/responder/pam/pam_LOCAL_domain.c @@ -0,0 +1,476 @@ +/* + SSSD + + PAM e credentials + + Copyright (C) Sumit Bose <sbose@redhat.com> 2009 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <time.h> +#include <security/pam_modules.h> + +#include "util/util.h" +#include "db/sysdb.h" +#include "util/sha512crypt.h" +#include "providers/data_provider.h" +#include "responder/pam/pamsrv.h" + + +#define NULL_CHECK_OR_JUMP(var, msg, ret, err, label) do { \ + if (var == NULL) { \ + DEBUG(1, (msg)); \ + ret = (err); \ + goto label; \ + } \ +} while(0) + +#define NEQ_CHECK_OR_JUMP(var, val, msg, ret, err, label) do { \ + if (var != (val)) { \ + DEBUG(1, (msg)); \ + ret = (err); \ + goto label; \ + } \ +} while(0) + + +struct LOCAL_request { + struct tevent_context *ev; + struct sysdb_ctx *dbctx; + struct sysdb_attrs *mod_attrs; + struct sysdb_handle *handle; + + struct ldb_result *res; + int error; + + struct pam_auth_req *preq; +}; + +static void prepare_reply(struct LOCAL_request *lreq) +{ + struct pam_data *pd; + + pd = lreq->preq->pd; + + if (lreq->error != EOK && pd->pam_status == PAM_SUCCESS) + pd->pam_status = PAM_SYSTEM_ERR; + + lreq->preq->callback(lreq->preq); +} + +static void set_user_attr_done(struct tevent_req *req) +{ + struct LOCAL_request *lreq; + int ret; + + lreq = tevent_req_callback_data(req, struct LOCAL_request); + + ret = sysdb_transaction_commit_recv(req); + if (ret) { + DEBUG(2, ("set_user_attr failed.\n")); + lreq->error =ret; + } + + prepare_reply(lreq); +} + +static void set_user_attr_req_done(struct tevent_req *subreq); +static void set_user_attr_req(struct tevent_req *req) +{ + struct LOCAL_request *lreq = tevent_req_callback_data(req, + struct LOCAL_request); + struct tevent_req *subreq; + int ret; + + DEBUG(4, ("entering set_user_attr_req\n")); + + ret = sysdb_transaction_recv(req, lreq, &lreq->handle); + if (ret) { + lreq->error = ret; + return prepare_reply(lreq); + } + + subreq = sysdb_set_user_attr_send(lreq, lreq->ev, lreq->handle, + lreq->preq->domain, + lreq->preq->pd->user, + lreq->mod_attrs, SYSDB_MOD_REP); + if (!subreq) { + /* cancel transaction */ + talloc_zfree(lreq->handle); + lreq->error = ret; + return prepare_reply(lreq); + } + tevent_req_set_callback(subreq, set_user_attr_req_done, lreq); +} + +static void set_user_attr_req_done(struct tevent_req *subreq) +{ + struct LOCAL_request *lreq = tevent_req_callback_data(subreq, + struct LOCAL_request); + struct tevent_req *req; + int ret; + + ret = sysdb_set_user_attr_recv(subreq); + talloc_zfree(subreq); + + DEBUG(4, ("set_user_attr_callback, status [%d][%s]\n", ret, strerror(ret))); + + if (ret) { + lreq->error = ret; + goto fail; + } + + req = sysdb_transaction_commit_send(lreq, lreq->ev, lreq->handle); + if (!req) { + lreq->error = ENOMEM; + goto fail; + } + tevent_req_set_callback(req, set_user_attr_done, lreq); + + return; + +fail: + DEBUG(2, ("set_user_attr failed.\n")); + + /* cancel transaction */ + talloc_zfree(lreq->handle); + + prepare_reply(lreq); +} + +static void do_successful_login(struct LOCAL_request *lreq) +{ + struct tevent_req *req; + int ret; + + lreq->mod_attrs = sysdb_new_attrs(lreq); + NULL_CHECK_OR_JUMP(lreq->mod_attrs, ("sysdb_new_attrs failed.\n"), + lreq->error, ENOMEM, done); + + ret = sysdb_attrs_add_long(lreq->mod_attrs, + SYSDB_LAST_LOGIN, (long)time(NULL)); + NEQ_CHECK_OR_JUMP(ret, EOK, ("sysdb_attrs_add_long failed.\n"), + lreq->error, ret, done); + + ret = sysdb_attrs_add_long(lreq->mod_attrs, SYSDB_FAILED_LOGIN_ATTEMPTS, 0L); + NEQ_CHECK_OR_JUMP(ret, EOK, ("sysdb_attrs_add_long failed.\n"), + lreq->error, ret, done); + + req = sysdb_transaction_send(lreq, lreq->ev, lreq->dbctx); + if (!req) { + lreq->error = ENOMEM; + goto done; + } + tevent_req_set_callback(req, set_user_attr_req, lreq); + + return; + +done: + + prepare_reply(lreq); +} + +static void do_failed_login(struct LOCAL_request *lreq) +{ + struct tevent_req *req; + int ret; + int failedLoginAttempts; + struct pam_data *pd; + + pd = lreq->preq->pd; + pd->pam_status = PAM_AUTH_ERR; +/* TODO: maybe add more inteligent delay calculation */ + pd->response_delay = 3; + + lreq->mod_attrs = sysdb_new_attrs(lreq); + NULL_CHECK_OR_JUMP(lreq->mod_attrs, ("sysdb_new_attrs failed.\n"), + lreq->error, ENOMEM, done); + + ret = sysdb_attrs_add_long(lreq->mod_attrs, + SYSDB_LAST_FAILED_LOGIN, (long)time(NULL)); + NEQ_CHECK_OR_JUMP(ret, EOK, ("sysdb_attrs_add_long failed.\n"), + lreq->error, ret, done); + + failedLoginAttempts = ldb_msg_find_attr_as_int(lreq->res->msgs[0], + SYSDB_FAILED_LOGIN_ATTEMPTS, + 0); + failedLoginAttempts++; + + ret = sysdb_attrs_add_long(lreq->mod_attrs, + SYSDB_FAILED_LOGIN_ATTEMPTS, + (long)failedLoginAttempts); + NEQ_CHECK_OR_JUMP(ret, EOK, ("sysdb_attrs_add_long failed.\n"), + lreq->error, ret, done); + + req = sysdb_transaction_send(lreq, lreq->ev, lreq->dbctx); + if (!req) { + lreq->error = ENOMEM; + goto done; + } + tevent_req_set_callback(req, set_user_attr_req, lreq); + + return; + +done: + + prepare_reply(lreq); +} + +static void do_pam_acct_mgmt(struct LOCAL_request *lreq) +{ + const char *disabled; + struct pam_data *pd; + + pd = lreq->preq->pd; + + disabled = ldb_msg_find_attr_as_string(lreq->res->msgs[0], + SYSDB_DISABLED, NULL); + if ((disabled != NULL) && + (strncasecmp(disabled, "false",5) != 0) && + (strncasecmp(disabled, "no",2) != 0) ) { + pd->pam_status = PAM_PERM_DENIED; + } + + prepare_reply(lreq); +} + +static void do_pam_chauthtok(struct LOCAL_request *lreq) +{ + struct tevent_req *req; + int ret; + char *newauthtok; + char *salt; + char *new_hash; + struct pam_data *pd; + + pd = lreq->preq->pd; + + newauthtok = talloc_strndup(lreq, (char *) pd->newauthtok, + pd->newauthtok_size); + NULL_CHECK_OR_JUMP(newauthtok, ("talloc_strndup failed.\n"), lreq->error, + ENOMEM, done); + memset(pd->newauthtok, 0, pd->newauthtok_size); + + if (strlen(newauthtok) == 0) { + /* TODO: should we allow null passwords via a config option ? */ + DEBUG(1, ("Empty passwords are not allowed!")); + ret = EINVAL; + goto done; + } + + ret = s3crypt_gen_salt(lreq, &salt); + NEQ_CHECK_OR_JUMP(ret, EOK, ("Salt generation failed.\n"), + lreq->error, ret, done); + DEBUG(4, ("Using salt [%s]\n", salt)); + + ret = s3crypt_sha512(lreq, newauthtok, salt, &new_hash); + NEQ_CHECK_OR_JUMP(ret, EOK, ("Hash generation failed.\n"), + lreq->error, ret, done); + DEBUG(4, ("New hash [%s]\n", new_hash)); + memset(newauthtok, 0, pd->newauthtok_size); + + lreq->mod_attrs = sysdb_new_attrs(lreq); + NULL_CHECK_OR_JUMP(lreq->mod_attrs, ("sysdb_new_attrs failed.\n"), + lreq->error, ENOMEM, done); + + ret = sysdb_attrs_add_string(lreq->mod_attrs, SYSDB_PWD, new_hash); + NEQ_CHECK_OR_JUMP(ret, EOK, ("sysdb_attrs_add_string failed.\n"), + lreq->error, ret, done); + + ret = sysdb_attrs_add_long(lreq->mod_attrs, + "lastPasswordChange", (long)time(NULL)); + NEQ_CHECK_OR_JUMP(ret, EOK, ("sysdb_attrs_add_long failed.\n"), + lreq->error, ret, done); + + req = sysdb_transaction_send(lreq, lreq->ev, lreq->dbctx); + if (!req) { + lreq->error = ENOMEM; + goto done; + } + tevent_req_set_callback(req, set_user_attr_req, lreq); + + return; +done: + + prepare_reply(lreq); +} + +static void local_handler_callback(void *pvt, int ldb_status, + struct ldb_result *res) +{ + struct LOCAL_request *lreq; + const char *username = NULL; + const char *password = NULL; + char *newauthtok = NULL; + char *new_hash = NULL; + char *authtok = NULL; + struct pam_data *pd; + int ret; + + lreq = talloc_get_type(pvt, struct LOCAL_request); + pd = lreq->preq->pd; + + DEBUG(4, ("pam_handler_callback called with ldb_status [%d].\n", + ldb_status)); + + NEQ_CHECK_OR_JUMP(ldb_status, LDB_SUCCESS, ("ldb search failed.\n"), + lreq->error, sysdb_error_to_errno(ldb_status), done); + + + if (res->count < 1) { + DEBUG(4, ("No user found with filter ["SYSDB_PWNAM_FILTER"]\n", + pd->user)); + pd->pam_status = PAM_USER_UNKNOWN; + goto done; + } else if (res->count > 1) { + DEBUG(4, ("More than one object found with filter ["SYSDB_PWNAM_FILTER"]\n")); + lreq->error = EFAULT; + goto done; + } + + username = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_NAME, NULL); + if (strcmp(username, pd->user) != 0) { + DEBUG(1, ("Expected username [%s] get [%s].\n", pd->user, username)); + lreq->error = EINVAL; + goto done; + } + + lreq->res = res; + + switch (pd->cmd) { + case SSS_PAM_AUTHENTICATE: + case SSS_PAM_CHAUTHTOK: + case SSS_PAM_CHAUTHTOK_PRELIM: + if ((pd->cmd == SSS_PAM_CHAUTHTOK || + pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) && + lreq->preq->cctx->priv == 1) { +/* TODO: maybe this is a candiate for an explicit audit message. */ + DEBUG(4, ("allowing root to reset a password.\n")); + break; + } + authtok = talloc_strndup(lreq, (char *) pd->authtok, + pd->authtok_size); + NULL_CHECK_OR_JUMP(authtok, ("talloc_strndup failed.\n"), + lreq->error, ENOMEM, done); + memset(pd->authtok, 0, pd->authtok_size); + + password = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_PWD, NULL); + NULL_CHECK_OR_JUMP(password, ("No password stored.\n"), + lreq->error, LDB_ERR_NO_SUCH_ATTRIBUTE, done); + DEBUG(4, ("user: [%s], password hash: [%s]\n", username, password)); + + ret = s3crypt_sha512(lreq, authtok, password, &new_hash); + memset(authtok, 0, pd->authtok_size); + NEQ_CHECK_OR_JUMP(ret, EOK, ("nss_sha512_crypt failed.\n"), + lreq->error, ret, done); + + DEBUG(4, ("user: [%s], new hash: [%s]\n", username, new_hash)); + + if (strcmp(new_hash, password) != 0) { + DEBUG(1, ("Passwords do not match.\n")); + do_failed_login(lreq); + return; + } + + break; + } + + switch (pd->cmd) { + case SSS_PAM_AUTHENTICATE: + do_successful_login(lreq); + return; + break; + case SSS_PAM_CHAUTHTOK: + do_pam_chauthtok(lreq); + return; + break; + case SSS_PAM_ACCT_MGMT: + do_pam_acct_mgmt(lreq); + return; + break; + case SSS_PAM_SETCRED: + break; + case SSS_PAM_OPEN_SESSION: + break; + case SSS_PAM_CLOSE_SESSION: + break; + case SSS_PAM_CHAUTHTOK_PRELIM: + break; + default: + lreq->error = EINVAL; + DEBUG(1, ("Unknown PAM task [%d].\n")); + } + +done: + if (pd->authtok != NULL) + memset(pd->authtok, 0, pd->authtok_size); + if (authtok != NULL) + memset(authtok, 0, pd->authtok_size); + if (pd->newauthtok != NULL) + memset(pd->newauthtok, 0, pd->newauthtok_size); + if (newauthtok != NULL) + memset(newauthtok, 0, pd->newauthtok_size); + + prepare_reply(lreq); +} + +int LOCAL_pam_handler(struct pam_auth_req *preq) +{ + int ret; + struct LOCAL_request *lreq; + + static const char *attrs[] = {SYSDB_NAME, + SYSDB_PWD, + SYSDB_DISABLED, + SYSDB_LAST_LOGIN, + "lastPasswordChange", + "accountExpires", + SYSDB_FAILED_LOGIN_ATTEMPTS, + "passwordHint", + "passwordHistory", + SYSDB_LAST_FAILED_LOGIN, + NULL}; + + DEBUG(4, ("LOCAL pam handler.\n")); + + lreq = talloc_zero(preq, struct LOCAL_request); + if (!lreq) { + return ENOMEM; + } + + ret = sysdb_get_ctx_from_list(preq->cctx->rctx->db_list, + preq->domain, &lreq->dbctx); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + talloc_free(lreq); + return ret; + } + lreq->ev = preq->cctx->ev; + lreq->preq = preq; + + preq->pd->pam_status = PAM_SUCCESS; + + ret = sysdb_get_user_attr(lreq, lreq->dbctx, + preq->domain, preq->pd->user, attrs, + local_handler_callback, lreq); + + if (ret != EOK) { + DEBUG(1, ("sysdb_get_user_attr failed.\n")); + talloc_free(lreq); + return ret; + } + + return EOK; +} |