diff options
author | Stephen Gallagher <sgallagh@redhat.com> | 2010-02-18 07:49:04 -0500 |
---|---|---|
committer | Stephen Gallagher <sgallagh@redhat.com> | 2010-02-18 13:48:45 -0500 |
commit | 1c48b5a62f73234ed26bb20f0ab345ab61cda0ab (patch) | |
tree | 0b6cddd567a862e1a7b5df23764869782a62ca78 /src/responder/pam | |
parent | 8c56df3176f528fe0260974b3bf934173c4651ea (diff) | |
download | sssd-1c48b5a62f73234ed26bb20f0ab345ab61cda0ab.tar.gz sssd-1c48b5a62f73234ed26bb20f0ab345ab61cda0ab.tar.bz2 sssd-1c48b5a62f73234ed26bb20f0ab345ab61cda0ab.zip |
Rename server/ directory to src/
Also update BUILD.txt
Diffstat (limited to 'src/responder/pam')
-rw-r--r-- | src/responder/pam/pam_LOCAL_domain.c | 476 | ||||
-rw-r--r-- | src/responder/pam/pamsrv.c | 224 | ||||
-rw-r--r-- | src/responder/pam/pamsrv.h | 57 | ||||
-rw-r--r-- | src/responder/pam/pamsrv_cmd.c | 1181 | ||||
-rw-r--r-- | src/responder/pam/pamsrv_dp.c | 142 |
5 files changed, 2080 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; +} diff --git a/src/responder/pam/pamsrv.c b/src/responder/pam/pamsrv.c new file mode 100644 index 00000000..84b13dc4 --- /dev/null +++ b/src/responder/pam/pamsrv.c @@ -0,0 +1,224 @@ +/* + SSSD + + PAM Responder + + Copyright (C) Simo Sorce <ssorce@redhat.com> 2009 + 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 <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <string.h> +#include <sys/time.h> +#include <errno.h> + +#include "popt.h" +#include "util/util.h" +#include "db/sysdb.h" +#include "confdb/confdb.h" +#include "dbus/dbus.h" +#include "sbus/sssd_dbus.h" +#include "responder/common/responder_packet.h" +#include "providers/data_provider.h" +#include "monitor/monitor_interfaces.h" +#include "sbus/sbus_client.h" +#include "responder/pam/pamsrv.h" + +#define SSS_PAM_SBUS_SERVICE_VERSION 0x0001 +#define SSS_PAM_SBUS_SERVICE_NAME "pam" + +static int service_reload(DBusMessage *message, struct sbus_connection *conn); + +struct sbus_method monitor_pam_methods[] = { + { MON_CLI_METHOD_PING, monitor_common_pong }, + { MON_CLI_METHOD_RELOAD, service_reload }, + { MON_CLI_METHOD_RES_INIT, monitor_common_res_init }, + { NULL, NULL } +}; + +struct sbus_interface monitor_pam_interface = { + MONITOR_INTERFACE, + MONITOR_PATH, + SBUS_DEFAULT_VTABLE, + monitor_pam_methods, + NULL +}; + +static int service_reload(DBusMessage *message, struct sbus_connection *conn) { + /* Monitor calls this function when we need to reload + * our configuration information. Perform whatever steps + * are needed to update the configuration objects. + */ + + /* Send an empty reply to acknowledge receipt */ + return monitor_common_pong(message, conn); +} + +static struct sbus_method pam_dp_methods[] = { + { NULL, NULL } +}; + +struct sbus_interface pam_dp_interface = { + DP_INTERFACE, + DP_PATH, + SBUS_DEFAULT_VTABLE, + pam_dp_methods, + NULL +}; + + +static void pam_dp_reconnect_init(struct sbus_connection *conn, int status, void *pvt) +{ + struct be_conn *be_conn = talloc_get_type(pvt, struct be_conn); + int ret; + + /* Did we reconnect successfully? */ + if (status == SBUS_RECONNECT_SUCCESS) { + DEBUG(1, ("Reconnected to the Data Provider.\n")); + + /* Identify ourselves to the data provider */ + ret = dp_common_send_id(be_conn->conn, + DATA_PROVIDER_VERSION, + "PAM", be_conn->domain->name); + /* all fine */ + if (ret == EOK) return; + } + + /* Handle failure */ + DEBUG(0, ("Could not reconnect to %s provider.\n", + be_conn->domain->name)); + + /* FIXME: kill the frontend and let the monitor restart it ? */ + /* pam_shutdown(rctx); */ +} + +static errno_t pam_get_config(struct pam_ctx *pctx, + struct resp_ctx *rctx, + struct confdb_ctx *cdb) +{ + int ret = EOK; + ret = confdb_get_int(cdb, pctx, CONFDB_PAM_CONF_ENTRY, + CONFDB_PAM_CRED_TIMEOUT, 0, + &pctx->cred_expiration); + return ret; +} + +static int pam_process_init(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct confdb_ctx *cdb) +{ + struct sss_cmd_table *pam_cmds; + struct be_conn *iter; + struct pam_ctx *pctx; + int ret, max_retries; + + pctx = talloc_zero(mem_ctx, struct pam_ctx); + if (!pctx) { + return ENOMEM; + } + + pam_cmds = get_pam_cmds(); + ret = sss_process_init(pctx, ev, cdb, + pam_cmds, + SSS_PAM_SOCKET_NAME, + SSS_PAM_PRIV_SOCKET_NAME, + CONFDB_PAM_CONF_ENTRY, + SSS_PAM_SBUS_SERVICE_NAME, + SSS_PAM_SBUS_SERVICE_VERSION, + &monitor_pam_interface, + "PAM", &pam_dp_interface, + &pctx->rctx); + if (ret != EOK) { + return ret; + } + + pctx->rctx->pvt_ctx = pctx; + ret = pam_get_config(pctx, pctx->rctx, pctx->rctx->cdb); + + /* Enable automatic reconnection to the Data Provider */ + + /* FIXME: "retries" is too generic, either get it from a global config + * or specify these retries are about the sbus connections to DP */ + ret = confdb_get_int(pctx->rctx->cdb, pctx->rctx, CONFDB_PAM_CONF_ENTRY, + CONFDB_SERVICE_RECON_RETRIES, 3, &max_retries); + if (ret != EOK) { + DEBUG(0, ("Failed to set up automatic reconnection\n")); + return ret; + } + + for (iter = pctx->rctx->be_conns; iter; iter = iter->next) { + sbus_reconnect_init(iter->conn, max_retries, + pam_dp_reconnect_init, iter); + } + + return EOK; +} + +int main(int argc, const char *argv[]) +{ + int opt; + poptContext pc; + struct main_context *main_ctx; + int ret; + + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_MAIN_OPTS + { NULL } + }; + + pc = poptGetContext(argv[0], argc, argv, long_options, 0); + while((opt = poptGetNextOpt(pc)) != -1) { + switch(opt) { + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + return 1; + } + } + + poptFreeContext(pc); + + /* set up things like debug, signals, daemonization, etc... */ + debug_log_file = "sssd_pam"; + + ret = server_setup("sssd[pam]", 0, CONFDB_PAM_CONF_ENTRY, &main_ctx); + if (ret != EOK) return 2; + + ret = die_if_parent_died(); + if (ret != EOK) { + /* This is not fatal, don't return */ + DEBUG(2, ("Could not set up to exit when parent process does\n")); + } + + ret = pam_process_init(main_ctx, + main_ctx->event_ctx, + main_ctx->confdb_ctx); + if (ret != EOK) return 3; + + /* loop on main */ + server_loop(main_ctx); + + return 0; +} + diff --git a/src/responder/pam/pamsrv.h b/src/responder/pam/pamsrv.h new file mode 100644 index 00000000..60f9c66a --- /dev/null +++ b/src/responder/pam/pamsrv.h @@ -0,0 +1,57 @@ +/* + Authors: + Simo Sorce <ssorce@redhat.com> + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2009 Red Hat + + 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/>. +*/ + +#ifndef __PAMSRV_H__ +#define __PAMSRV_H__ + +#include <security/pam_appl.h> +#include "util/util.h" +#include "sbus/sssd_dbus.h" +#include "responder/common/responder.h" + +struct pam_auth_req; + +typedef void (pam_dp_callback_t)(struct pam_auth_req *preq); + +struct pam_ctx { + int cred_expiration; + struct resp_ctx *rctx; +}; + +struct pam_auth_req { + struct cli_ctx *cctx; + struct sss_domain_info *domain; + + struct pam_data *pd; + + pam_dp_callback_t *callback; + + bool check_provider; + void *data; +}; + +struct sss_cmd_table *get_pam_cmds(void); + +int pam_dp_send_req(struct pam_auth_req *preq, int timeout); + +int LOCAL_pam_handler(struct pam_auth_req *preq); + +#endif /* __PAMSRV_H__ */ diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c new file mode 100644 index 00000000..37aad829 --- /dev/null +++ b/src/responder/pam/pamsrv_cmd.c @@ -0,0 +1,1181 @@ +/* + SSSD + + PAM Responder + + Copyright (C) Simo Sorce <ssorce@redhat.com> 2009 + 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 "util/util.h" +#include "db/sysdb.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 void pam_reply(struct pam_auth_req *preq); + +static int extract_authtok(uint32_t *type, uint32_t *size, uint8_t **tok, uint8_t *body, size_t blen, size_t *c) { + uint32_t data_size; + + if (blen-(*c) < 2*sizeof(uint32_t)) return EINVAL; + + memcpy(&data_size, &body[*c], sizeof(uint32_t)); + *c += sizeof(uint32_t); + if (data_size < sizeof(uint32_t) || (*c)+(data_size) > blen) return EINVAL; + *size = data_size - sizeof(uint32_t); + + memcpy(type, &body[*c], sizeof(uint32_t)); + *c += sizeof(uint32_t); + + *tok = body+(*c); + + *c += (*size); + + return EOK; +} + +static int extract_string(char **var, uint8_t *body, size_t blen, size_t *c) { + uint32_t size; + uint8_t *str; + + if (blen-(*c) < sizeof(uint32_t)+1) return EINVAL; + + memcpy(&size, &body[*c], sizeof(uint32_t)); + *c += sizeof(uint32_t); + if (*c+size > blen) return EINVAL; + + str = body+(*c); + + if (str[size-1]!='\0') return EINVAL; + + *c += size; + + *var = (char *) str; + + return EOK; +} + +static int extract_uint32_t(uint32_t *var, uint8_t *body, size_t blen, size_t *c) { + uint32_t size; + + if (blen-(*c) < 2*sizeof(uint32_t)) return EINVAL; + + memcpy(&size, &body[*c], sizeof(uint32_t)); + *c += sizeof(uint32_t); + + memcpy(var, &body[*c], sizeof(uint32_t)); + *c += sizeof(uint32_t); + + return EOK; +} + +static int pam_parse_in_data_v2(struct sss_names_ctx *snctx, + struct pam_data *pd, + uint8_t *body, size_t blen) +{ + size_t c; + uint32_t type; + uint32_t size; + char *pam_user; + int ret; + uint32_t terminator = SSS_END_OF_PAM_REQUEST; + + if (blen < 4*sizeof(uint32_t)+2 || + ((uint32_t *)body)[0] != SSS_START_OF_PAM_REQUEST || + memcmp(&body[blen - sizeof(uint32_t)], &terminator, sizeof(uint32_t)) != 0) { + DEBUG(1, ("Received data is invalid.\n")); + return EINVAL; + } + + c = sizeof(uint32_t); + do { + memcpy(&type, &body[c], sizeof(uint32_t)); + c += sizeof(uint32_t); + if (c > blen) return EINVAL; + + switch(type) { + case SSS_PAM_ITEM_USER: + ret = extract_string(&pam_user, body, blen, &c); + if (ret != EOK) return ret; + + ret = sss_parse_name(pd, snctx, pam_user, + &pd->domain, &pd->user); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_SERVICE: + ret = extract_string(&pd->service, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_TTY: + ret = extract_string(&pd->tty, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_RUSER: + ret = extract_string(&pd->ruser, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_RHOST: + ret = extract_string(&pd->rhost, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_CLI_PID: + ret = extract_uint32_t(&pd->cli_pid, + body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_AUTHTOK: + ret = extract_authtok(&pd->authtok_type, &pd->authtok_size, + &pd->authtok, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_PAM_ITEM_NEWAUTHTOK: + ret = extract_authtok(&pd->newauthtok_type, + &pd->newauthtok_size, + &pd->newauthtok, body, blen, &c); + if (ret != EOK) return ret; + break; + case SSS_END_OF_PAM_REQUEST: + if (c != blen) return EINVAL; + break; + default: + DEBUG(1,("Ignoring unknown data type [%d].\n", type)); + size = ((uint32_t *)&body[c])[0]; + c += size+sizeof(uint32_t); + } + } while(c < blen); + + if (pd->user == NULL || *pd->user == '\0') return EINVAL; + + DEBUG_PAM_DATA(4, pd); + + return EOK; + +} + +static int pam_parse_in_data_v3(struct sss_names_ctx *snctx, + struct pam_data *pd, + uint8_t *body, size_t blen) +{ + int ret; + + ret = pam_parse_in_data_v2(snctx, pd, body, blen); + if (ret != EOK) { + DEBUG(1, ("pam_parse_in_data_v2 failed.\n")); + return ret; + } + + if (pd->cli_pid == 0) { + DEBUG(1, ("Missing client PID.\n")); + return EINVAL; + } + + return EOK; +} + +static int pam_parse_in_data(struct sss_names_ctx *snctx, + struct pam_data *pd, + uint8_t *body, size_t blen) +{ + int start; + int end; + int last; + int ret; + + last = blen - 1; + end = 0; + + /* user name */ + for (start = end; end < last; end++) if (body[end] == '\0') break; + if (body[end++] != '\0') return EINVAL; + + ret = sss_parse_name(pd, snctx, (char *)&body[start], &pd->domain, &pd->user); + if (ret != EOK) return ret; + + for (start = end; end < last; end++) if (body[end] == '\0') break; + if (body[end++] != '\0') return EINVAL; + pd->service = (char *) &body[start]; + + for (start = end; end < last; end++) if (body[end] == '\0') break; + if (body[end++] != '\0') return EINVAL; + pd->tty = (char *) &body[start]; + + for (start = end; end < last; end++) if (body[end] == '\0') break; + if (body[end++] != '\0') return EINVAL; + pd->ruser = (char *) &body[start]; + + for (start = end; end < last; end++) if (body[end] == '\0') break; + if (body[end++] != '\0') return EINVAL; + pd->rhost = (char *) &body[start]; + + start = end; + pd->authtok_type = (int) body[start]; + + start += sizeof(uint32_t); + pd->authtok_size = (int) body[start]; + + start += sizeof(uint32_t); + end = start + pd->authtok_size; + if (pd->authtok_size == 0) { + pd->authtok = NULL; + } else { + if (end <= blen) { + pd->authtok = (uint8_t *) &body[start]; + } else { + DEBUG(1, ("Invalid authtok size: %d\n", pd->authtok_size)); + return EINVAL; + } + } + + start = end; + pd->newauthtok_type = (int) body[start]; + + start += sizeof(uint32_t); + pd->newauthtok_size = (int) body[start]; + + start += sizeof(uint32_t); + end = start + pd->newauthtok_size; + + if (pd->newauthtok_size == 0) { + pd->newauthtok = NULL; + } else { + if (end <= blen) { + pd->newauthtok = (uint8_t *) &body[start]; + } else { + DEBUG(1, ("Invalid newauthtok size: %d\n", pd->newauthtok_size)); + return EINVAL; + } + } + + DEBUG_PAM_DATA(4, pd); + + return EOK; +} + +/*=Save-Last-Login-State===================================================*/ + +struct set_last_login_state { + struct tevent_context *ev; + struct sysdb_ctx *dbctx; + + struct sss_domain_info *dom; + const char *username; + struct sysdb_attrs *attrs; + + struct sysdb_handle *handle; + + struct ldb_result *res; +}; + +static void set_last_login_trans_done(struct tevent_req *subreq); +static void set_last_login_attrs_done(struct tevent_req *subreq); +static void set_last_login_done(struct tevent_req *subreq); + +static struct tevent_req *set_last_login_send(TALLOC_CTX *memctx, + struct tevent_context *ev, + struct sysdb_ctx *dbctx, + struct sss_domain_info *dom, + const char *username, + struct sysdb_attrs *attrs) +{ + struct tevent_req *req, *subreq; + struct set_last_login_state *state; + + req = tevent_req_create(memctx, &state, struct set_last_login_state); + if (!req) { + return NULL; + } + + state->ev = ev; + state->dbctx = dbctx; + state->dom = dom; + state->username = username; + state->attrs = attrs; + state->handle = NULL; + + subreq = sysdb_transaction_send(state, state->ev, state->dbctx); + if (!subreq) { + talloc_free(req); + return NULL; + } + tevent_req_set_callback(subreq, set_last_login_trans_done, req); + + return req; +} + +static void set_last_login_trans_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct set_last_login_state *state = tevent_req_data(req, + struct set_last_login_state); + int ret; + + ret = sysdb_transaction_recv(subreq, state, &state->handle); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(1, ("Unable to acquire sysdb transaction lock\n")); + tevent_req_error(req, ret); + return; + } + + subreq = sysdb_set_user_attr_send(state, state->ev, state->handle, + state->dom, state->username, + state->attrs, SYSDB_MOD_REP); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, set_last_login_attrs_done, req); +} + +static void set_last_login_attrs_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct set_last_login_state *state = tevent_req_data(req, + struct set_last_login_state); + int ret; + + ret = sysdb_set_user_attr_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(4, ("set_user_attr_callback, status [%d][%s]\n", + ret, strerror(ret))); + tevent_req_error(req, ret); + return; + } + + subreq = sysdb_transaction_commit_send(state, state->ev, state->handle); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, set_last_login_done, req); +} + +static void set_last_login_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + int ret; + + ret = sysdb_transaction_commit_recv(subreq); + if (ret != EOK) { + DEBUG(2, ("set_last_login failed.\n")); + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static int set_last_login_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +/*=========================================================================*/ + + +static void set_last_login_reply(struct tevent_req *req); + +static errno_t set_last_login(struct pam_auth_req *preq) +{ + struct tevent_req *req; + struct sysdb_ctx *dbctx; + struct sysdb_attrs *attrs; + errno_t ret; + + attrs = sysdb_new_attrs(preq); + if (!attrs) { + ret = ENOMEM; + goto fail; + } + + ret = sysdb_attrs_add_time_t(attrs, SYSDB_LAST_ONLINE_AUTH, time(NULL)); + if (ret != EOK) { + goto fail; + } + + ret = sysdb_get_ctx_from_list(preq->cctx->rctx->db_list, preq->domain, + &dbctx); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb context not found for this domain!\n")); + goto fail; + } + + req = set_last_login_send(preq, preq->cctx->ev, dbctx, + preq->domain, preq->pd->user, attrs); + if (!req) { + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(req, set_last_login_reply, preq); + + return EOK; + +fail: + return ret; +} + +static void set_last_login_reply(struct tevent_req *req) +{ + struct pam_auth_req *preq = tevent_req_callback_data(req, + struct pam_auth_req); + int ret; + + ret = set_last_login_recv(req); + if (ret != EOK) { + preq->pd->pam_status = PAM_SYSTEM_ERR; + } else { + preq->pd->last_auth_saved = true; + } + + preq->callback(preq); +} + +static void pam_reply_delay(struct tevent_context *ev, struct tevent_timer *te, + struct timeval tv, void *pvt) +{ + struct pam_auth_req *preq; + + DEBUG(4, ("pam_reply_delay get called.\n")); + + preq = talloc_get_type(pvt, struct pam_auth_req); + + pam_reply(preq); +} + +static void pam_cache_auth_done(struct tevent_req *req); + +static void pam_reply(struct pam_auth_req *preq) +{ + struct cli_ctx *cctx; + uint8_t *body; + size_t blen; + int ret; + int32_t resp_c; + int32_t resp_size; + struct response_data *resp; + int p; + struct timeval tv; + struct tevent_timer *te; + struct pam_data *pd; + struct tevent_req *req; + struct sysdb_ctx *sysdb; + struct pam_ctx *pctx; + uint32_t user_info_type; + + pd = preq->pd; + cctx = preq->cctx; + + DEBUG(4, ("pam_reply get called.\n")); + + if (pd->pam_status == PAM_AUTHINFO_UNAVAIL) { + switch(pd->cmd) { + case SSS_PAM_AUTHENTICATE: + if ((preq->domain != NULL) && + (preq->domain->cache_credentials == true) && + (pd->offline_auth == false)) { + + /* do auth with offline credentials */ + pd->offline_auth = true; + + ret = sysdb_get_ctx_from_list(preq->cctx->rctx->db_list, + preq->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for " + "domain [%s]!\n", preq->domain->name)); + goto done; + } + + pctx = talloc_get_type(preq->cctx->rctx->pvt_ctx, + struct pam_ctx); + + req = sysdb_cache_auth_send(preq, preq->cctx->ev, sysdb, + preq->domain, pd->user, + pd->authtok, pd->authtok_size, + pctx->rctx->cdb); + if (req == NULL) { + DEBUG(1, ("Failed to setup offline auth.\n")); + /* this error is not fatal, continue */ + } else { + tevent_req_set_callback(req, pam_cache_auth_done, preq); + return; + } + } + break; + case SSS_PAM_CHAUTHTOK_PRELIM: + case SSS_PAM_CHAUTHTOK: + DEBUG(5, ("Password change not possible while offline.\n")); + pd->pam_status = PAM_AUTHTOK_ERR; + user_info_type = SSS_PAM_USER_INFO_OFFLINE_CHPASS; + pam_add_response(pd, SSS_PAM_USER_INFO, sizeof(uint32_t), + (const uint8_t *) &user_info_type); + break; +/* TODO: we need the pam session cookie here to make sure that cached + * authentication was successful */ + case SSS_PAM_SETCRED: + case SSS_PAM_ACCT_MGMT: + case SSS_PAM_OPEN_SESSION: + case SSS_PAM_CLOSE_SESSION: + DEBUG(2, ("Assuming offline authentication setting status for " + "pam call %d to PAM_SUCCESS.\n", pd->cmd)); + pd->pam_status = PAM_SUCCESS; + break; + default: + DEBUG(1, ("Unknown PAM call [%d].\n", pd->cmd)); + pd->pam_status = PAM_MODULE_UNKNOWN; + } + } + + if (pd->response_delay > 0) { + ret = gettimeofday(&tv, NULL); + if (ret != EOK) { + DEBUG(1, ("gettimeofday failed [%d][%s].\n", + errno, strerror(errno))); + goto done; + } + tv.tv_sec += pd->response_delay; + tv.tv_usec = 0; + pd->response_delay = 0; + + te = tevent_add_timer(cctx->ev, cctx, tv, pam_reply_delay, preq); + if (te == NULL) { + DEBUG(1, ("Failed to add event pam_reply_delay.\n")); + goto done; + } + + return; + } + + /* If this was a successful login, save the lastLogin time */ + if (pd->cmd == SSS_PAM_AUTHENTICATE && + pd->pam_status == PAM_SUCCESS && + preq->domain->cache_credentials && + !pd->offline_auth && + !pd->last_auth_saved && + NEED_CHECK_PROVIDER(preq->domain->provider)) { + ret = set_last_login(preq); + if (ret != EOK) { + goto done; + } + + return; + } + + ret = sss_packet_new(cctx->creq, 0, sss_packet_get_cmd(cctx->creq->in), + &cctx->creq->out); + if (ret != EOK) { + goto done; + } + + if (pd->domain != NULL) { + pam_add_response(pd, SSS_PAM_DOMAIN_NAME, strlen(pd->domain)+1, + (uint8_t *) pd->domain); + } + + resp_c = 0; + resp_size = 0; + resp = pd->resp_list; + while(resp != NULL) { + resp_c++; + resp_size += resp->len; + resp = resp->next; + } + + ret = sss_packet_grow(cctx->creq->out, sizeof(int32_t) + + sizeof(int32_t) + + resp_c * 2* sizeof(int32_t) + + resp_size); + if (ret != EOK) { + goto done; + } + + sss_packet_get_body(cctx->creq->out, &body, &blen); + DEBUG(4, ("blen: %d\n", blen)); + p = 0; + + memcpy(&body[p], &pd->pam_status, sizeof(int32_t)); + p += sizeof(int32_t); + + memcpy(&body[p], &resp_c, sizeof(int32_t)); + p += sizeof(int32_t); + + resp = pd->resp_list; + while(resp != NULL) { + memcpy(&body[p], &resp->type, sizeof(int32_t)); + p += sizeof(int32_t); + memcpy(&body[p], &resp->len, sizeof(int32_t)); + p += sizeof(int32_t); + memcpy(&body[p], resp->data, resp->len); + p += resp->len; + + resp = resp->next; + } + +done: + sss_cmd_done(cctx, preq); +} + +static void pam_cache_auth_done(struct tevent_req *req) +{ + int ret; + struct pam_auth_req *preq = tevent_req_callback_data(req, + struct pam_auth_req); + uint32_t resp_type; + size_t resp_len; + uint8_t *resp; + time_t expire_date = 0; + time_t delayed_until = -1; + long long dummy; + + ret = sysdb_cache_auth_recv(req, &expire_date, &delayed_until); + talloc_zfree(req); + + switch (ret) { + case EOK: + preq->pd->pam_status = PAM_SUCCESS; + + resp_type = SSS_PAM_USER_INFO_OFFLINE_AUTH; + resp_len = sizeof(uint32_t) + sizeof(long long); + resp = talloc_size(preq->pd, resp_len); + if (resp == NULL) { + DEBUG(1, ("talloc_size failed, cannot prepare user info.\n")); + } else { + memcpy(resp, &resp_type, sizeof(uint32_t)); + dummy = (long long) expire_date; + memcpy(resp+sizeof(uint32_t), &dummy, sizeof(long long)); + ret = pam_add_response(preq->pd, SSS_PAM_USER_INFO, resp_len, + (const uint8_t *) resp); + if (ret != EOK) { + DEBUG(1, ("pam_add_response failed.\n")); + } + } + break; + case ENOENT: + preq->pd->pam_status = PAM_AUTHINFO_UNAVAIL; + break; + case EINVAL: + preq->pd->pam_status = PAM_AUTH_ERR; + break; + case EACCES: + preq->pd->pam_status = PAM_PERM_DENIED; + if (delayed_until >= 0) { + resp_type = SSS_PAM_USER_INFO_OFFLINE_AUTH_DELAYED; + resp_len = sizeof(uint32_t) + sizeof(long long); + resp = talloc_size(preq->pd, resp_len); + if (resp == NULL) { + DEBUG(1, ("talloc_size failed, cannot prepare user info.\n")); + } else { + memcpy(resp, &resp_type, sizeof(uint32_t)); + dummy = (long long) delayed_until; + memcpy(resp+sizeof(uint32_t), &dummy, sizeof(long long)); + ret = pam_add_response(preq->pd, SSS_PAM_USER_INFO, resp_len, + (const uint8_t *) resp); + if (ret != EOK) { + DEBUG(1, ("pam_add_response failed.\n")); + } + } + } + break; + default: + preq->pd->pam_status = PAM_SYSTEM_ERR; + } + + pam_reply(preq); + return; +} + +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 sysdb_ctx *sysdb; + struct pam_auth_req *preq; + struct pam_data *pd; + uint8_t *body; + size_t blen; + int timeout; + int ret; + uint32_t terminator = SSS_END_OF_PAM_REQUEST; + preq = talloc_zero(cctx, struct pam_auth_req); + if (!preq) { + return ENOMEM; + } + preq->cctx = cctx; + + preq->pd = talloc_zero(preq, struct pam_data); + if (!preq->pd) { + talloc_free(preq); + return ENOMEM; + } + pd = preq->pd; + + sss_packet_get_body(cctx->creq->in, &body, &blen); + if (blen >= sizeof(uint32_t) && + memcmp(&body[blen - sizeof(uint32_t)], &terminator, sizeof(uint32_t)) != 0) { + DEBUG(1, ("Received data not terminated.\n")); + ret = EINVAL; + goto done; + } + + pd->cmd = pam_cmd; + pd->priv = cctx->priv; + + switch (cctx->cli_protocol_version->version) { + case 1: + ret = pam_parse_in_data(cctx->rctx->names, pd, body, blen); + break; + case 2: + ret = pam_parse_in_data_v2(cctx->rctx->names, pd, body, blen); + break; + case 3: + ret = pam_parse_in_data_v3(cctx->rctx->names, pd, body, blen); + break; + default: + DEBUG(1, ("Illegal protocol version [%d].\n", + cctx->cli_protocol_version->version)); + ret = EINVAL; + } + if (ret != EOK) { + 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) { + ret = ENOENT; + goto done; + } + preq->domain = dom; + } + else { + 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; + } + + if (preq->domain->provider == NULL) { + DEBUG(1, ("Domain [%s] has no auth provider.\n", preq->domain->name)); + ret = EINVAL; + goto done; + } + + /* When auth is requested always search the provider first, + * do not rely on cached data unless the provider is completely + * offline */ + if (NEED_CHECK_PROVIDER(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 = sss_dp_send_acct_req(preq->cctx->rctx, preq, + pam_check_user_dp_callback, preq, + timeout, preq->domain->name, + false, SSS_DP_INITGROUPS, + preq->pd->user, 0); + } + else { + preq->check_provider = NEED_CHECK_PROVIDER(preq->domain->provider); + + ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, + preq->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + goto done; + } + ret = sysdb_getpwnam(preq, 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 sysdb_ctx *sysdb; + int ret; + + if (err_maj) { + 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)); + } + + /* always try to see if we have the user in cache even if the provider + * returned an error */ + ret = sysdb_get_ctx_from_list(preq->cctx->rctx->db_list, + preq->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + goto done; + } + ret = sysdb_getpwnam(preq, 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; + struct sysdb_ctx *sysdb; + uint64_t cacheExpire; + bool call_provider = false; + time_t 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: + cacheExpire = ldb_msg_find_attr_as_uint64(res->msgs[0], + SYSDB_CACHE_EXPIRE, 0); + if (cacheExpire < 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 = sss_dp_send_acct_req(preq->cctx->rctx, preq, + pam_check_user_dp_callback, preq, + timeout, preq->domain->name, + false, SSS_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 (NEED_CHECK_PROVIDER(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 = sss_dp_send_acct_req(preq->cctx->rctx, preq, + pam_check_user_dp_callback, + preq, timeout, + preq->domain->name, + false, SSS_DP_USER, + preq->pd->user, 0); + } + else { + preq->check_provider = NEED_CHECK_PROVIDER(preq->domain->provider); + + ret = sysdb_get_ctx_from_list(preq->cctx->rctx->db_list, + preq->domain, &sysdb); + if (ret != EOK) { + DEBUG(0, ("Fatal: Sysdb CTX not found for this domain!\n")); + preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(preq); + return; + } + ret = sysdb_getpwnam(preq, 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 */ + preq->pd->pw_uid = + ldb_msg_find_attr_as_int(res->msgs[0], SYSDB_UIDNUM, -1); + if (preq->pd->pw_uid == -1) { + DEBUG(1, ("Failed to find uid for user [%s] in domain [%s].\n", + preq->pd->user, preq->pd->domain)); + preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(preq); + } + + preq->pd->gr_gid = + ldb_msg_find_attr_as_int(res->msgs[0], SYSDB_GIDNUM, -1); + if (preq->pd->gr_gid == -1) { + DEBUG(1, ("Failed to find gid for user [%s] in domain [%s].\n", + preq->pd->user, preq->pd->domain)); + preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(preq); + } + + 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 (!NEED_CHECK_PROVIDER(preq->domain->provider)) { + preq->callback = pam_reply; + ret = LOCAL_pam_handler(preq); + } + else { + preq->callback = pam_reply; + ret = pam_dp_send_req(preq, SSS_CLI_SOCKET_TIMEOUT/2); + DEBUG(4, ("pam_dp_send_req returned %d\n", ret)); + } + + if (ret != EOK) { + preq->pd->pam_status = PAM_SYSTEM_ERR; + pam_reply(preq); + } +} + +static int pam_cmd_authenticate(struct cli_ctx *cctx) { + DEBUG(4, ("entering pam_cmd_authenticate\n")); + return pam_forwarder(cctx, SSS_PAM_AUTHENTICATE); +} + +static int pam_cmd_setcred(struct cli_ctx *cctx) { + DEBUG(4, ("entering pam_cmd_setcred\n")); + return pam_forwarder(cctx, SSS_PAM_SETCRED); +} + +static int pam_cmd_acct_mgmt(struct cli_ctx *cctx) { + DEBUG(4, ("entering pam_cmd_acct_mgmt\n")); + return pam_forwarder(cctx, SSS_PAM_ACCT_MGMT); +} + +static int pam_cmd_open_session(struct cli_ctx *cctx) { + DEBUG(4, ("entering pam_cmd_open_session\n")); + return pam_forwarder(cctx, SSS_PAM_OPEN_SESSION); +} + +static int pam_cmd_close_session(struct cli_ctx *cctx) { + DEBUG(4, ("entering pam_cmd_close_session\n")); + return pam_forwarder(cctx, SSS_PAM_CLOSE_SESSION); +} + +static int pam_cmd_chauthtok(struct cli_ctx *cctx) { + DEBUG(4, ("entering pam_cmd_chauthtok\n")); + return pam_forwarder(cctx, SSS_PAM_CHAUTHTOK); +} + +static int pam_cmd_chauthtok_prelim(struct cli_ctx *cctx) { + DEBUG(4, ("entering pam_cmd_chauthtok_prelim\n")); + return pam_forwarder(cctx, SSS_PAM_CHAUTHTOK_PRELIM); +} + +struct cli_protocol_version *register_cli_protocol_version(void) +{ + static struct cli_protocol_version pam_cli_protocol_version[] = { + {3, "2009-09-14", "make cli_pid mandatory"}, + {2, "2009-05-12", "new format <type><size><data>"}, + {1, "2008-09-05", "initial version, \\0 terminated strings"}, + {0, NULL, NULL} + }; + + return pam_cli_protocol_version; +} + +struct sss_cmd_table *get_pam_cmds(void) +{ + static struct sss_cmd_table sss_cmds[] = { + {SSS_GET_VERSION, sss_cmd_get_version}, + {SSS_PAM_AUTHENTICATE, pam_cmd_authenticate}, + {SSS_PAM_SETCRED, pam_cmd_setcred}, + {SSS_PAM_ACCT_MGMT, pam_cmd_acct_mgmt}, + {SSS_PAM_OPEN_SESSION, pam_cmd_open_session}, + {SSS_PAM_CLOSE_SESSION, pam_cmd_close_session}, + {SSS_PAM_CHAUTHTOK, pam_cmd_chauthtok}, + {SSS_PAM_CHAUTHTOK_PRELIM, pam_cmd_chauthtok_prelim}, + {SSS_CLI_NULL, NULL} + }; + + return sss_cmds; +} diff --git a/src/responder/pam/pamsrv_dp.c b/src/responder/pam/pamsrv_dp.c new file mode 100644 index 00000000..071d09b8 --- /dev/null +++ b/src/responder/pam/pamsrv_dp.c @@ -0,0 +1,142 @@ +/* + SSSD + + NSS Responder - Data Provider Interfaces + + Copyright (C) Simo Sorce <ssorce@redhat.com> 2008 + + 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 <sys/time.h> +#include <time.h> + +#include <talloc.h> +#include <security/pam_modules.h> + +#include "util/util.h" +#include "responder/common/responder_packet.h" +#include "providers/data_provider.h" +#include "sbus/sbus_client.h" +#include "responder/pam/pamsrv.h" + +static void pam_dp_process_reply(DBusPendingCall *pending, void *ptr) +{ + DBusError dbus_error; + DBusMessage* msg; + int ret; + int type; + struct pam_auth_req *preq; + + preq = talloc_get_type(ptr, struct pam_auth_req); + + dbus_error_init(&dbus_error); + + dbus_pending_call_block(pending); + msg = dbus_pending_call_steal_reply(pending); + if (msg == NULL) { + DEBUG(0, ("Severe error. A reply callback was called but no reply was received and no timeout occurred\n")); + preq->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + + + type = dbus_message_get_type(msg); + switch (type) { + case DBUS_MESSAGE_TYPE_METHOD_RETURN: + ret = dp_unpack_pam_response(msg, preq->pd, &dbus_error); + if (!ret) { + DEBUG(0, ("Failed to parse reply.\n")); + preq->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + DEBUG(4, ("received: [%d][%s]\n", preq->pd->pam_status, preq->pd->domain)); + break; + case DBUS_MESSAGE_TYPE_ERROR: + DEBUG(0, ("Reply error.\n")); + preq->pd->pam_status = PAM_SYSTEM_ERR; + break; + default: + DEBUG(0, ("Default... what now?.\n")); + preq->pd->pam_status = PAM_SYSTEM_ERR; + } + + +done: + dbus_pending_call_unref(pending); + dbus_message_unref(msg); + preq->callback(preq); +} + +int pam_dp_send_req(struct pam_auth_req *preq, int timeout) +{ + struct pam_data *pd = preq->pd; + struct be_conn *be_conn; + DBusMessage *msg; + DBusPendingCall *pending_reply; + DBusConnection *dbus_conn; + dbus_bool_t ret; + int res; + + /* double check dp_ctx has actually been initialized. + * in some pathological cases it may happen that nss starts up before + * dp connection code is actually able to establish a connection. + */ + res = sss_dp_get_domain_conn(preq->cctx->rctx, + preq->domain->name, &be_conn); + if (res != EOK) { + DEBUG(1, ("The Data Provider connection for %s is not available!" + " This maybe a bug, it shouldn't happen!\n", preq->domain)); + return EIO; + } + dbus_conn = sbus_get_connection(be_conn->conn); + + msg = dbus_message_new_method_call(NULL, + DP_PATH, + DP_INTERFACE, + DP_METHOD_PAMHANDLER); + if (msg == NULL) { + DEBUG(0,("Out of memory?!\n")); + return ENOMEM; + } + + + DEBUG(4, ("Sending request with the following data:\n")); + DEBUG_PAM_DATA(4, pd); + + ret = dp_pack_pam_request(msg, pd); + if (!ret) { + DEBUG(1,("Failed to build message\n")); + return EIO; + } + + ret = dbus_connection_send_with_reply(dbus_conn, msg, &pending_reply, timeout); + if (!ret || pending_reply == NULL) { + /* + * Critical Failure + * We can't communicate on this connection + * We'll drop it using the default destructor. + */ + DEBUG(0, ("D-BUS send failed.\n")); + dbus_message_unref(msg); + return EIO; + } + + dbus_pending_call_set_notify(pending_reply, + pam_dp_process_reply, preq, NULL); + dbus_message_unref(msg); + + return EOK; +} + |