From 1c48b5a62f73234ed26bb20f0ab345ab61cda0ab Mon Sep 17 00:00:00 2001 From: Stephen Gallagher Date: Thu, 18 Feb 2010 07:49:04 -0500 Subject: Rename server/ directory to src/ Also update BUILD.txt --- src/providers/krb5/krb5_auth.c | 1193 +++++++++++++++++++++++++++++++++ src/providers/krb5/krb5_auth.h | 91 +++ src/providers/krb5/krb5_become_user.c | 61 ++ src/providers/krb5/krb5_child.c | 1030 ++++++++++++++++++++++++++++ src/providers/krb5/krb5_common.c | 356 ++++++++++ src/providers/krb5/krb5_common.h | 72 ++ src/providers/krb5/krb5_init.c | 152 +++++ src/providers/krb5/krb5_utils.c | 145 ++++ src/providers/krb5/krb5_utils.h | 39 ++ 9 files changed, 3139 insertions(+) create mode 100644 src/providers/krb5/krb5_auth.c create mode 100644 src/providers/krb5/krb5_auth.h create mode 100644 src/providers/krb5/krb5_become_user.c create mode 100644 src/providers/krb5/krb5_child.c create mode 100644 src/providers/krb5/krb5_common.c create mode 100644 src/providers/krb5/krb5_common.h create mode 100644 src/providers/krb5/krb5_init.c create mode 100644 src/providers/krb5/krb5_utils.c create mode 100644 src/providers/krb5/krb5_utils.h (limited to 'src/providers/krb5') diff --git a/src/providers/krb5/krb5_auth.c b/src/providers/krb5/krb5_auth.c new file mode 100644 index 00000000..a2dadc80 --- /dev/null +++ b/src/providers/krb5/krb5_auth.c @@ -0,0 +1,1193 @@ +/* + SSSD + + Kerberos 5 Backend Module + + Authors: + Sumit Bose + + 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 . +*/ + +#include +#include + +#include +#include +#include +#include + +#include + +#include "util/util.h" +#include "util/find_uid.h" +#include "db/sysdb.h" +#include "providers/child_common.h" +#include "providers/krb5/krb5_auth.h" +#include "providers/krb5/krb5_utils.h" + +#ifndef SSSD_LIBEXEC_PATH +#error "SSSD_LIBEXEC_PATH not defined" +#else +#define KRB5_CHILD SSSD_LIBEXEC_PATH"/krb5_child" +#endif + +static errno_t add_krb5_env(struct dp_option *opts, const char *ccname, + struct pam_data *pd) +{ + int ret; + const char *dummy; + char *env; + TALLOC_CTX *tmp_ctx = NULL; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(1, ("talloc_new failed.\n")); + return ENOMEM; + } + + if (ccname != NULL) { + env = talloc_asprintf(tmp_ctx, "%s=%s",CCACHE_ENV_NAME, ccname); + if (env == NULL) { + DEBUG(1, ("talloc_asprintf failed.\n")); + ret = ENOMEM; + goto done; + } + ret = pam_add_response(pd, SSS_PAM_ENV_ITEM, strlen(env)+1, + (uint8_t *) env); + if (ret != EOK) { + DEBUG(1, ("pam_add_response failed.\n")); + goto done; + } + } + + dummy = dp_opt_get_cstring(opts, KRB5_REALM); + if (dummy != NULL) { + env = talloc_asprintf(tmp_ctx, "%s=%s", SSSD_KRB5_REALM, dummy); + if (env == NULL) { + DEBUG(1, ("talloc_asprintf failed.\n")); + ret = ENOMEM; + goto done; + } + ret = pam_add_response(pd, SSS_PAM_ENV_ITEM, strlen(env)+1, + (uint8_t *) env); + if (ret != EOK) { + DEBUG(1, ("pam_add_response failed.\n")); + goto done; + } + } + + dummy = dp_opt_get_cstring(opts, KRB5_KDC); + if (dummy != NULL) { + env = talloc_asprintf(tmp_ctx, "%s=%s", SSSD_KRB5_KDC, dummy); + if (env == NULL) { + DEBUG(1, ("talloc_asprintf failed.\n")); + ret = ENOMEM; + goto done; + } + ret = pam_add_response(pd, SSS_PAM_ENV_ITEM, strlen(env)+1, + (uint8_t *) env); + if (ret != EOK) { + DEBUG(1, ("pam_add_response failed.\n")); + goto done; + } + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static errno_t check_if_ccache_file_is_used(uid_t uid, const char *ccname, + bool *result) +{ + int ret; + size_t offset = 0; + struct stat stat_buf; + const char *filename; + bool active; + + *result = false; + + if (ccname == NULL || *ccname == '\0') { + return EINVAL; + } + + if (strncmp(ccname, "FILE:", 5) == 0) { + offset = 5; + } + + filename = ccname + offset; + + if (filename[0] != '/') { + DEBUG(1, ("Only absolute path names are allowed")); + return EINVAL; + } + + ret = lstat(filename, &stat_buf); + + if (ret == -1 && errno != ENOENT) { + DEBUG(1, ("stat failed [%d][%s].\n", errno, strerror(errno))); + return errno; + } else if (ret == EOK) { + if (stat_buf.st_uid != uid) { + DEBUG(1, ("Cache file [%s] exists, but is owned by [%d] instead of " + "[%d].\n", filename, stat_buf.st_uid, uid)); + return EINVAL; + } + + if (!S_ISREG(stat_buf.st_mode)) { + DEBUG(1, ("Cache file [%s] exists, but is not a regular file.\n", + filename)); + return EINVAL; + } + } + + ret = check_if_uid_is_active(uid, &active); + if (ret != EOK) { + DEBUG(1, ("check_if_uid_is_active failed.\n")); + return ret; + } + + if (!active) { + DEBUG(5, ("User [%d] is not active\n", uid)); + } else { + DEBUG(9, ("User [%d] is still active, reusing ccache file [%s].\n", + uid, filename)); + *result = true; + } + return EOK; +} + +struct krb5_save_ccname_state { + struct tevent_context *ev; + struct sysdb_ctx *sysdb; + struct sysdb_handle *handle; + struct sss_domain_info *domain; + const char *name; + struct sysdb_attrs *attrs; +}; + +static void krb5_save_ccname_trans(struct tevent_req *subreq); +static void krb5_set_user_attr_done(struct tevent_req *subreq); + +static struct tevent_req *krb5_save_ccname_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sysdb_ctx *sysdb, + struct sss_domain_info *domain, + const char *name, + const char *ccname) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct krb5_save_ccname_state *state; + int ret; + + if (name == NULL || ccname == NULL) { + DEBUG(1, ("Missing user or ccache name.\n")); + return NULL; + } + + req = tevent_req_create(mem_ctx, &state, struct krb5_save_ccname_state); + if (req == NULL) { + DEBUG(1, ("tevent_req_create failed.\n")); + return NULL; + } + + state->ev = ev; + state->sysdb = sysdb; + state->handle = NULL; + state->domain = domain; + state->name = name; + + state->attrs = sysdb_new_attrs(state); + ret = sysdb_attrs_add_string(state->attrs, SYSDB_CCACHE_FILE, ccname); + if (ret != EOK) { + DEBUG(1, ("sysdb_attrs_add_string failed.\n")); + goto failed; + } + + subreq = sysdb_transaction_send(state, ev, sysdb); + if (subreq == NULL) { + goto failed; + } + tevent_req_set_callback(subreq, krb5_save_ccname_trans, req); + + return req; + +failed: + talloc_free(req); + return NULL; +} + +static void krb5_save_ccname_trans(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct krb5_save_ccname_state *state = tevent_req_data(req, + struct krb5_save_ccname_state); + int ret; + + ret = sysdb_transaction_recv(subreq, state, &state->handle); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret))); + tevent_req_error(req, ret); + return; + } + + subreq = sysdb_set_user_attr_send(state, state->ev, state->handle, + state->domain, state->name, + state->attrs, SYSDB_MOD_REP); + if (subreq == NULL) { + DEBUG(6, ("Error: Out of memory\n")); + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, krb5_set_user_attr_done, req); +} + +static void krb5_set_user_attr_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct krb5_save_ccname_state *state = tevent_req_data(req, + struct krb5_save_ccname_state); + int ret; + + ret = sysdb_set_user_attr_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret))); + tevent_req_error(req, ret); + return; + } + + subreq = sysdb_transaction_commit_send(state, state->ev, state->handle); + if (subreq == NULL) { + DEBUG(6, ("Error: Out of memory\n")); + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, sysdb_transaction_complete, req); + return; +} + +int krb5_save_ccname_recv(struct tevent_req *req) +{ + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +errno_t create_send_buffer(struct krb5child_req *kr, struct io_buffer **io_buf) +{ + struct io_buffer *buf; + size_t rp; + const char *keytab; + uint32_t validate; + + keytab = dp_opt_get_cstring(kr->krb5_ctx->opts, KRB5_KEYTAB); + if (keytab == NULL) { + DEBUG(1, ("Missing keytab option.\n")); + return EINVAL; + } + + validate = dp_opt_get_bool(kr->krb5_ctx->opts, KRB5_VALIDATE) ? 1 : 0; + + buf = talloc(kr, struct io_buffer); + if (buf == NULL) { + DEBUG(1, ("talloc failed.\n")); + return ENOMEM; + } + + buf->size = 9*sizeof(uint32_t) + strlen(kr->pd->upn) + strlen(kr->ccname) + + strlen(keytab) + + kr->pd->authtok_size; + if (kr->pd->cmd == SSS_PAM_CHAUTHTOK) { + buf->size += sizeof(uint32_t) + kr->pd->newauthtok_size; + } + + buf->data = talloc_size(kr, buf->size); + if (buf->data == NULL) { + DEBUG(1, ("talloc_size failed.\n")); + talloc_free(buf); + return ENOMEM; + } + + rp = 0; + COPY_UINT32(&buf->data[rp], &kr->pd->cmd, rp); + COPY_UINT32(&buf->data[rp], &kr->pd->pw_uid, rp); + COPY_UINT32(&buf->data[rp], &kr->pd->gr_gid, rp); + COPY_UINT32(&buf->data[rp], &validate, rp); + COPY_UINT32(&buf->data[rp], &kr->is_offline, rp); + + COPY_UINT32_VALUE(&buf->data[rp], strlen(kr->pd->upn), rp); + COPY_MEM(&buf->data[rp], kr->pd->upn, rp, strlen(kr->pd->upn)); + + COPY_UINT32_VALUE(&buf->data[rp], strlen(kr->ccname), rp); + COPY_MEM(&buf->data[rp], kr->ccname, rp, strlen(kr->ccname)); + + COPY_UINT32_VALUE(&buf->data[rp], strlen(keytab), rp); + COPY_MEM(&buf->data[rp], keytab, rp, strlen(keytab)); + + COPY_UINT32(&buf->data[rp], &kr->pd->authtok_size, rp); + COPY_MEM(&buf->data[rp], kr->pd->authtok, rp, kr->pd->authtok_size); + + if (kr->pd->cmd == SSS_PAM_CHAUTHTOK) { + COPY_UINT32(&buf->data[rp], &kr->pd->newauthtok_size, rp); + COPY_MEM(&buf->data[rp], kr->pd->newauthtok, + rp, kr->pd->newauthtok_size); + } + + *io_buf = buf; + + return EOK; +} + +static struct krb5_ctx *get_krb5_ctx(struct be_req *be_req) +{ + struct pam_data *pd; + + pd = talloc_get_type(be_req->req_data, struct pam_data); + + switch (pd->cmd) { + case SSS_PAM_AUTHENTICATE: + return talloc_get_type(be_req->be_ctx->bet_info[BET_AUTH].pvt_bet_data, + struct krb5_ctx); + break; + case SSS_PAM_CHAUTHTOK: + case SSS_PAM_CHAUTHTOK_PRELIM: + return talloc_get_type(be_req->be_ctx->bet_info[BET_CHPASS].pvt_bet_data, + struct krb5_ctx); + break; + default: + DEBUG(1, ("Unsupported PAM task.\n")); + return NULL; + } +} + +static void krb_reply(struct be_req *req, int dp_err, int result); + +static void krb5_child_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, void *pvt) +{ + struct krb5child_req *kr = talloc_get_type(pvt, struct krb5child_req); + struct be_req *be_req = kr->req; + struct pam_data *pd = kr->pd; + int ret; + + if (kr->timeout_handler == NULL) { + return; + } + + DEBUG(9, ("timeout for child [%d] reached.\n", kr->child_pid)); + + ret = kill(kr->child_pid, SIGKILL); + if (ret == -1) { + DEBUG(1, ("kill failed [%d][%s].\n", errno, strerror(errno))); + } + + talloc_zfree(kr); + + pd->pam_status = PAM_AUTHINFO_UNAVAIL; + be_mark_offline(be_req->be_ctx); + + krb_reply(be_req, DP_ERR_OFFLINE, pd->pam_status); +} + +static errno_t activate_child_timeout_handler(struct krb5child_req *kr) +{ + struct timeval tv; + + tv = tevent_timeval_current(); + tv = tevent_timeval_add(&tv, + dp_opt_get_int(kr->krb5_ctx->opts, + KRB5_AUTH_TIMEOUT), + 0); + kr->timeout_handler = tevent_add_timer(kr->req->be_ctx->ev, kr, tv, + krb5_child_timeout, kr); + if (kr->timeout_handler == NULL) { + DEBUG(1, ("tevent_add_timer failed.\n")); + return ENOMEM; + } + + return EOK; +} + +static int krb5_cleanup(void *ptr) +{ + struct krb5child_req *kr = talloc_get_type(ptr, struct krb5child_req); + + if (kr == NULL) return EOK; + + child_cleanup(kr->read_from_child_fd, kr->write_to_child_fd); + memset(kr, 0, sizeof(struct krb5child_req)); + + return EOK; +} + +static errno_t krb5_setup(struct be_req *req, struct krb5child_req **krb5_req) +{ + struct krb5child_req *kr = NULL; + struct krb5_ctx *krb5_ctx; + struct pam_data *pd; + errno_t err; + + pd = talloc_get_type(req->req_data, struct pam_data); + + krb5_ctx = get_krb5_ctx(req); + if (krb5_ctx == NULL) { + DEBUG(1, ("Kerberos context not available.\n")); + err = EINVAL; + goto failed; + } + + kr = talloc_zero(req, struct krb5child_req); + if (kr == NULL) { + DEBUG(1, ("talloc failed.\n")); + err = ENOMEM; + goto failed; + } + kr->read_from_child_fd = -1; + kr->write_to_child_fd = -1; + kr->is_offline = false; + kr->active_ccache_present = true; + talloc_set_destructor((TALLOC_CTX *) kr, krb5_cleanup); + + kr->pd = pd; + kr->req = req; + kr->krb5_ctx = krb5_ctx; + + *krb5_req = kr; + + return EOK; + +failed: + talloc_zfree(kr); + + return err; +} + +static errno_t fork_child(struct krb5child_req *kr) +{ + int pipefd_to_child[2]; + int pipefd_from_child[2]; + pid_t pid; + int ret; + errno_t err; + + ret = pipe(pipefd_from_child); + if (ret == -1) { + err = errno; + DEBUG(1, ("pipe failed [%d][%s].\n", errno, strerror(errno))); + return err; + } + ret = pipe(pipefd_to_child); + if (ret == -1) { + err = errno; + DEBUG(1, ("pipe failed [%d][%s].\n", errno, strerror(errno))); + return err; + } + + pid = fork(); + + if (pid == 0) { /* child */ + /* We need to keep the root privileges to read the keytab file if + * validation is enabled, otherwise we can drop them here and run + * krb5_child with user privileges. + * If authtok_size is zero we are offline and want to create an empty + * ccache file. In this case we can drop the privileges, too. */ + if (!dp_opt_get_bool(kr->krb5_ctx->opts, KRB5_VALIDATE) || + kr->pd->authtok_size == 0) { + ret = become_user(kr->pd->pw_uid, kr->pd->gr_gid); + if (ret != EOK) { + DEBUG(1, ("become_user failed.\n")); + return ret; + } + } + + err = exec_child(kr, + pipefd_to_child, pipefd_from_child, + KRB5_CHILD, kr->krb5_ctx->child_debug_fd); + if (err != EOK) { + DEBUG(1, ("Could not exec LDAP child: [%d][%s].\n", + err, strerror(err))); + return err; + } + } else if (pid > 0) { /* parent */ + kr->child_pid = pid; + kr->read_from_child_fd = pipefd_from_child[0]; + close(pipefd_from_child[1]); + kr->write_to_child_fd = pipefd_to_child[1]; + close(pipefd_to_child[0]); + fd_nonblocking(kr->read_from_child_fd); + fd_nonblocking(kr->write_to_child_fd); + + err = activate_child_timeout_handler(kr); + if (err != EOK) { + DEBUG(1, ("activate_child_timeout_handler failed.\n")); + } + + } else { /* error */ + err = errno; + DEBUG(1, ("fork failed [%d][%s].\n", errno, strerror(errno))); + return err; + } + + return EOK; +} + +struct handle_child_state { + struct tevent_context *ev; + struct krb5child_req *kr; + uint8_t *buf; + ssize_t len; +}; + +static void handle_child_step(struct tevent_req *subreq); +static void handle_child_done(struct tevent_req *subreq); + +static struct tevent_req *handle_child_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct krb5child_req *kr) +{ + struct tevent_req *req, *subreq; + struct handle_child_state *state; + struct io_buffer *buf; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct handle_child_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->kr = kr; + state->buf = NULL; + state->len = 0; + + ret = create_send_buffer(kr, &buf); + if (ret != EOK) { + DEBUG(1, ("create_send_buffer failed.\n")); + goto fail; + } + + ret = fork_child(kr); + if (ret != EOK) { + DEBUG(1, ("fork_child failed.\n")); + goto fail; + } + + subreq = write_pipe_send(state, ev, buf->data, buf->size, + kr->write_to_child_fd); + if (!subreq) { + ret = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, handle_child_step, req); + + return req; + +fail: + tevent_req_error(req, ret); + tevent_req_post(req, ev); + return req; +} + +static void handle_child_step(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct handle_child_state *state = tevent_req_data(req, + struct handle_child_state); + int ret; + + ret = write_pipe_recv(subreq); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + close(state->kr->write_to_child_fd); + state->kr->write_to_child_fd = -1; + + subreq = read_pipe_send(state, state->ev, state->kr->read_from_child_fd); + if (!subreq) { + tevent_req_error(req, ENOMEM); + return; + } + tevent_req_set_callback(subreq, handle_child_done, req); +} + +static void handle_child_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, + struct tevent_req); + struct handle_child_state *state = tevent_req_data(req, + struct handle_child_state); + int ret; + + ret = read_pipe_recv(subreq, state, &state->buf, &state->len); + talloc_zfree(subreq); + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + close(state->kr->read_from_child_fd); + state->kr->read_from_child_fd = -1; + + tevent_req_done(req); + return; +} + +static int handle_child_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uint8_t **buf, ssize_t *len) +{ + struct handle_child_state *state = tevent_req_data(req, + struct handle_child_state); + + TEVENT_REQ_RETURN_ON_ERROR(req); + + *buf = talloc_move(mem_ctx, &state->buf); + *len = state->len; + + return EOK; +} + +static void get_user_attr_done(void *pvt, int err, struct ldb_result *res); +static void krb5_resolve_done(struct tevent_req *req); +static void krb5_save_ccname_done(struct tevent_req *req); +static void krb5_child_done(struct tevent_req *req); +static void krb5_pam_handler_cache_done(struct tevent_req *treq); + +void krb5_pam_handler(struct be_req *be_req) +{ + struct pam_data *pd; + const char **attrs; + int pam_status = PAM_SYSTEM_ERR; + int dp_err = DP_ERR_FATAL; + int ret; + + pd = talloc_get_type(be_req->req_data, struct pam_data); + + switch (pd->cmd) { + case SSS_PAM_AUTHENTICATE: + case SSS_PAM_CHAUTHTOK: + case SSS_PAM_CHAUTHTOK_PRELIM: + break; + case SSS_PAM_ACCT_MGMT: + case SSS_PAM_SETCRED: + case SSS_PAM_OPEN_SESSION: + case SSS_PAM_CLOSE_SESSION: + pam_status = PAM_SUCCESS; + dp_err = DP_ERR_OK; + goto done; + break; + default: + DEBUG(4, ("krb5 does not handles pam task %d.\n", pd->cmd)); + pam_status = PAM_MODULE_UNKNOWN; + dp_err = DP_ERR_OK; + goto done; + } + + if (be_is_offline(be_req->be_ctx) && + (pd->cmd == SSS_PAM_CHAUTHTOK || pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM)) { + DEBUG(9, ("Password changes are not possible while offline.\n")); + pam_status = PAM_AUTHINFO_UNAVAIL; + dp_err = DP_ERR_OFFLINE; + goto done; + } + + attrs = talloc_array(be_req, const char *, 4); + if (attrs == NULL) { + goto done; + } + + attrs[0] = SYSDB_UPN; + attrs[1] = SYSDB_HOMEDIR; + attrs[2] = SYSDB_CCACHE_FILE; + attrs[3] = NULL; + + ret = sysdb_get_user_attr(be_req, be_req->be_ctx->sysdb, + be_req->be_ctx->domain, pd->user, attrs, + get_user_attr_done, be_req); + + if (ret) { + goto done; + } + + return; + +done: + pd->pam_status = pam_status; + + krb_reply(be_req, dp_err, pd->pam_status); +} + +static void get_user_attr_done(void *pvt, int err, struct ldb_result *res) +{ + struct be_req *be_req = talloc_get_type(pvt, struct be_req); + struct krb5_ctx *krb5_ctx; + struct krb5child_req *kr = NULL; + struct tevent_req *req; + krb5_error_code kerr; + int ret; + struct pam_data *pd = talloc_get_type(be_req->req_data, struct pam_data); + int pam_status=PAM_SYSTEM_ERR; + int dp_err = DP_ERR_FATAL; + const char *ccache_file = NULL; + const char *realm; + + ret = krb5_setup(be_req, &kr); + if (ret != EOK) { + DEBUG(1, ("krb5_setup failed.\n")); + goto failed; + } + + krb5_ctx = kr->krb5_ctx; + + if (err != LDB_SUCCESS) { + DEBUG(5, ("sysdb search for upn of user [%s] failed.\n", pd->user)); + goto failed; + } + + realm = dp_opt_get_cstring(krb5_ctx->opts, KRB5_REALM); + if (realm == NULL) { + DEBUG(1, ("Missing Kerberos realm.\n")); + goto failed; + } + + switch (res->count) { + case 0: + DEBUG(5, ("No attributes for user [%s] found.\n", pd->user)); + goto failed; + break; + + case 1: + pd->upn = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_UPN, NULL); + if (pd->upn == NULL) { + /* NOTE: this is a hack, works only in some environments */ + pd->upn = talloc_asprintf(be_req, "%s@%s", pd->user, realm); + if (pd->upn == NULL) { + DEBUG(1, ("failed to build simple upn.\n")); + goto failed; + } + DEBUG(9, ("Using simple UPN [%s].\n", pd->upn)); + } + + kr->homedir = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_HOMEDIR, + NULL); + if (kr->homedir == NULL) { + DEBUG(4, ("Home directory for user [%s] not known.\n", pd->user)); + } + + ccache_file = ldb_msg_find_attr_as_string(res->msgs[0], + SYSDB_CCACHE_FILE, + NULL); + if (ccache_file != NULL) { + ret = check_if_ccache_file_is_used(pd->pw_uid, ccache_file, + &kr->active_ccache_present); + if (ret != EOK) { + DEBUG(1, ("check_if_ccache_file_is_used failed.\n")); + goto failed; + } + + kerr = check_for_valid_tgt(ccache_file, realm, pd->upn, + &kr->valid_tgt_present); + if (kerr != 0) { + DEBUG(1, ("check_for_valid_tgt failed.\n")); + goto failed; + } + } else { + kr->active_ccache_present = false; + kr->valid_tgt_present = false; + DEBUG(4, ("No ccache file for user [%s] found.\n", pd->user)); + } + DEBUG(9, ("Ccache_file is [%s] and is %s active and TGT is %s valid.\n", + ccache_file ? ccache_file : "not set", + kr->active_ccache_present ? "" : "not", + kr->valid_tgt_present ? "" : "not")); + kr->ccname = ccache_file; + break; + + default: + DEBUG(1, ("A user search by name (%s) returned > 1 results!\n", + pd->user)); + goto failed; + break; + } + + req = be_resolve_server_send(kr, be_req->be_ctx->ev, be_req->be_ctx, + krb5_ctx->service->name); + if (req == NULL) { + DEBUG(1, ("handle_child_send failed.\n")); + goto failed; + } + + tevent_req_set_callback(req, krb5_resolve_done, kr); + + return; + +failed: + talloc_free(kr); + + pd->pam_status = pam_status; + krb_reply(be_req, dp_err, pd->pam_status); +} + +static void krb5_resolve_done(struct tevent_req *req) +{ + struct krb5child_req *kr = tevent_req_callback_data(req, + struct krb5child_req); + int ret; + int pam_status = PAM_SYSTEM_ERR; + int dp_err = DP_ERR_FATAL; + struct pam_data *pd = kr->pd; + struct be_req *be_req = kr->req; + char *msg; + size_t offset = 0; + + ret = be_resolve_server_recv(req, &kr->srv); + talloc_zfree(req); + if (ret) { + /* all servers have been tried and none + * was found good, setting offline, + * but we still have to call the child to setup + * the ccache file. */ + be_mark_offline(be_req->be_ctx); + kr->is_offline = true; + } + + if (kr->ccname == NULL || + (be_is_offline(be_req->be_ctx) && !kr->active_ccache_present && + !kr->valid_tgt_present) || + (!be_is_offline(be_req->be_ctx) && !kr->active_ccache_present)) { + DEBUG(9, ("Recreating ccache file.\n")); + if (kr->ccname != NULL) { + if (strncmp(kr->ccname, "FILE:", 5) == 0) { + offset = 5; + } + if (kr->ccname[offset] != '/') { + DEBUG(1, ("Ccache file name [%s] is not an absolute path.\n", + kr->ccname + offset)); + goto done; + } + ret = unlink(kr->ccname + offset); + if (ret == -1 && errno != ENOENT) { + DEBUG(1, ("unlink [%s] failed [%d][%s].\n", kr->ccname, + errno, strerror(errno))); + goto done; + } + } + kr->ccname = expand_ccname_template(kr, kr, + dp_opt_get_cstring(kr->krb5_ctx->opts, + KRB5_CCNAME_TMPL) + ); + if (kr->ccname == NULL) { + DEBUG(1, ("expand_ccname_template failed.\n")); + goto done; + } + } + + if (be_is_offline(be_req->be_ctx)) { + DEBUG(9, ("Preparing for offline operation.\n")); + kr->is_offline = true; + + if (kr->valid_tgt_present) { + DEBUG(9, ("Valid TGT available, nothing to do.\n")); + msg = talloc_asprintf(pd, "%s=%s", CCACHE_ENV_NAME, kr->ccname); + if (msg == NULL) { + DEBUG(1, ("talloc_asprintf failed.\n")); + goto done; + } + + ret = pam_add_response(pd, SSS_PAM_ENV_ITEM, strlen(msg) + 1, + (uint8_t *) msg); + if (ret != EOK) { + DEBUG(1, ("pam_add_response failed.\n")); + } + + pam_status = PAM_AUTHINFO_UNAVAIL; + dp_err = DP_ERR_OFFLINE; + goto done; + } + memset(pd->authtok, 0, pd->authtok_size); + pd->authtok_size = 0; + + if (kr->active_ccache_present) { + req = krb5_save_ccname_send(kr, be_req->be_ctx->ev, + be_req->be_ctx->sysdb, + be_req->be_ctx->domain, pd->user, + kr->ccname); + if (req == NULL) { + DEBUG(1, ("krb5_save_ccname_send failed.\n")); + goto done; + } + + tevent_req_set_callback(req, krb5_save_ccname_done, kr); + return; + } + } + + req = handle_child_send(kr, be_req->be_ctx->ev, kr); + if (req == NULL) { + DEBUG(1, ("handle_child_send failed.\n")); + goto done; + } + + tevent_req_set_callback(req, krb5_child_done, kr); + return; + +done: + talloc_free(kr); + pd->pam_status = pam_status; + krb_reply(be_req, dp_err, pd->pam_status); +} + +static void krb5_child_done(struct tevent_req *req) +{ + struct krb5child_req *kr = tevent_req_callback_data(req, + struct krb5child_req); + struct pam_data *pd = kr->pd; + struct be_req *be_req = kr->req; + int ret; + uint8_t *buf = NULL; + ssize_t len = -1; + ssize_t pref_len; + int p; + int32_t *msg_status; + int32_t *msg_type; + int32_t *msg_len; + int pam_status = PAM_SYSTEM_ERR; + int dp_err = DP_ERR_FATAL; + + ret = handle_child_recv(req, pd, &buf, &len); + talloc_zfree(kr->timeout_handler); + talloc_zfree(req); + if (ret != EOK) { + DEBUG(1, ("child failed (%d [%s])\n", ret, strerror(ret))); + goto done; + } + + if ((size_t) len < 3*sizeof(int32_t)) { + DEBUG(1, ("message too short.\n")); + goto done; + } + + p=0; + msg_status = ((int32_t *)(buf+p)); + p += sizeof(int32_t); + + msg_type = ((int32_t *)(buf+p)); + p += sizeof(int32_t); + + msg_len = ((int32_t *)(buf+p)); + p += sizeof(int32_t); + + DEBUG(4, ("child response [%d][%d][%d].\n", *msg_status, *msg_type, + *msg_len)); + + if ((p + *msg_len) != len) { + DEBUG(1, ("message format error.\n")); + goto done; + } + + if (*msg_status != PAM_SUCCESS && *msg_status != PAM_AUTHINFO_UNAVAIL) { + pam_status = *msg_status; + dp_err = DP_ERR_OK; + + ret = pam_add_response(pd, *msg_type, *msg_len, &buf[p]); + if (ret != EOK) { + DEBUG(1, ("pam_add_response failed.\n")); + } + + goto done; + } else { + pd->pam_status = *msg_status; + } + + if (*msg_status == PAM_SUCCESS && pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) { + pam_status = PAM_SUCCESS; + dp_err = DP_ERR_OK; + goto done; + } + + pref_len = strlen(CCACHE_ENV_NAME)+1; + if (*msg_len > pref_len && + strncmp((const char *) &buf[p], CCACHE_ENV_NAME"=", pref_len) == 0) { + kr->ccname = talloc_strndup(kr, (char *) &buf[p+pref_len], + *msg_len-pref_len); + if (kr->ccname == NULL) { + DEBUG(1, ("talloc_strndup failed.\n")); + goto done; + } + } else { + DEBUG(1, ("Missing ccache name in child response [%.*s].\n", *msg_len, + &buf[p])); + goto done; + } + + if (*msg_status == PAM_AUTHINFO_UNAVAIL) { + if (kr->srv != NULL) { + fo_set_port_status(kr->srv, PORT_NOT_WORKING); + } + be_mark_offline(be_req->be_ctx); + kr->is_offline = true; + } else if (kr->srv != NULL) { + fo_set_port_status(kr->srv, PORT_WORKING); + } + + struct sysdb_attrs *attrs; + attrs = sysdb_new_attrs(kr); + ret = sysdb_attrs_add_string(attrs, SYSDB_CCACHE_FILE, kr->ccname); + if (ret != EOK) { + DEBUG(1, ("sysdb_attrs_add_string failed.\n")); + goto done; + } + + req = krb5_save_ccname_send(kr, be_req->be_ctx->ev, be_req->be_ctx->sysdb, + be_req->be_ctx->domain, pd->user, kr->ccname); + if (req == NULL) { + DEBUG(1, ("krb5_save_ccname_send failed.\n")); + goto done; + } + + tevent_req_set_callback(req, krb5_save_ccname_done, kr); + return; +done: + talloc_free(kr); + pd->pam_status = pam_status; + krb_reply(be_req, dp_err, pd->pam_status); +} + +static void krb5_save_ccname_done(struct tevent_req *req) +{ + struct krb5child_req *kr = tevent_req_callback_data(req, + struct krb5child_req); + struct pam_data *pd = kr->pd; + struct be_req *be_req = kr->req; + struct krb5_ctx *krb5_ctx = kr->krb5_ctx; + int pam_status = PAM_SYSTEM_ERR; + int dp_err = DP_ERR_FATAL; + int ret; + char *password = NULL; + + if (pd->cmd == SSS_PAM_AUTHENTICATE) { + ret = add_krb5_env(krb5_ctx->opts, kr->ccname, pd); + if (ret != EOK) { + DEBUG(1, ("add_krb5_env failed.\n")); + goto failed; + } + } + + ret = sysdb_set_user_attr_recv(req); + talloc_zfree(req); + if (ret != EOK) { + DEBUG(1, ("Saving ccache name failed.\n")); + goto failed; + } + + if (kr->is_offline) { + DEBUG(4, ("Backend is marked offline, retry later!\n")); + pam_status = PAM_AUTHINFO_UNAVAIL; + dp_err = DP_ERR_OFFLINE; + goto failed; + } + + if (be_req->be_ctx->domain->cache_credentials == TRUE) { + + /* password caching failures are not fatal errors */ + pd->pam_status = PAM_SUCCESS; + + switch(pd->cmd) { + case SSS_PAM_AUTHENTICATE: + case SSS_PAM_CHAUTHTOK_PRELIM: + password = talloc_size(be_req, pd->authtok_size + 1); + if (password != NULL) { + memcpy(password, pd->authtok, pd->authtok_size); + password[pd->authtok_size] = '\0'; + } + break; + case SSS_PAM_CHAUTHTOK: + password = talloc_size(be_req, pd->newauthtok_size + 1); + if (password != NULL) { + memcpy(password, pd->newauthtok, pd->newauthtok_size); + password[pd->newauthtok_size] = '\0'; + } + break; + default: + DEBUG(0, ("unsupported PAM command [%d].\n", pd->cmd)); + } + + if (password == NULL) { + DEBUG(0, ("password not available, offline auth may not work.\n")); + goto failed; + } + + talloc_set_destructor((TALLOC_CTX *)password, password_destructor); + + req = sysdb_cache_password_send(be_req, be_req->be_ctx->ev, + be_req->be_ctx->sysdb, NULL, + be_req->be_ctx->domain, pd->user, + password); + if (req == NULL) { + DEBUG(2, ("cache_password_send failed, offline auth may not work.\n")); + goto failed; + } + tevent_req_set_callback(req, krb5_pam_handler_cache_done, be_req); + return; + } + + pam_status = PAM_SUCCESS; + dp_err = DP_ERR_OK; + +failed: + talloc_free(kr); + + pd->pam_status = pam_status; + krb_reply(be_req, dp_err, pd->pam_status); +} + +static void krb5_pam_handler_cache_done(struct tevent_req *subreq) +{ + struct be_req *be_req = tevent_req_callback_data(subreq, struct be_req); + int ret; + + /* password caching failures are not fatal errors */ + ret = sysdb_cache_password_recv(subreq); + talloc_zfree(subreq); + + /* so we just log it any return */ + if (ret) { + DEBUG(2, ("Failed to cache password (%d)[%s]!?\n", + ret, strerror(ret))); + } + + krb_reply(be_req, DP_ERR_OK, PAM_SUCCESS); +} + +static void krb_reply(struct be_req *req, int dp_err, int result) +{ + req->fn(req, dp_err, result, NULL); +} + diff --git a/src/providers/krb5/krb5_auth.h b/src/providers/krb5/krb5_auth.h new file mode 100644 index 00000000..a011af89 --- /dev/null +++ b/src/providers/krb5/krb5_auth.h @@ -0,0 +1,91 @@ +/* + SSSD + + Kerberos Backend, private header file + + Authors: + Sumit Bose + + 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 . +*/ + +#ifndef __KRB5_AUTH_H__ +#define __KRB5_AUTH_H__ + +#include "util/sss_krb5.h" +#include "providers/dp_backend.h" +#include "providers/krb5/krb5_common.h" + +#define CCACHE_ENV_NAME "KRB5CCNAME" +#define SSSD_KRB5_CHANGEPW_PRINCIPLE "SSSD_KRB5_CHANGEPW_PRINCIPLE" + +typedef enum { INIT_PW, INIT_KT, RENEW, VALIDATE } action_type; + +struct krb5child_req { + pid_t child_pid; + int read_from_child_fd; + int write_to_child_fd; + + struct be_req *req; + struct pam_data *pd; + struct krb5_ctx *krb5_ctx; + + struct tevent_timer *timeout_handler; + + const char *ccname; + const char *homedir; + bool is_offline; + struct fo_server *srv; + bool active_ccache_present; + bool valid_tgt_present; +}; + +struct fo_service; + +struct krb5_ctx { + /* opts taken from kinit */ + /* in seconds */ + krb5_deltat starttime; + krb5_deltat lifetime; + krb5_deltat rlife; + + int forwardable; + int proxiable; + int addresses; + + int not_forwardable; + int not_proxiable; + int no_addresses; + + int verbose; + + char* principal_name; + char* service_name; + char* keytab_name; + char* k5_cache_name; + char* k4_cache_name; + + action_type action; + + struct dp_option *opts; + struct krb5_service *service; + int child_debug_fd; +}; + +void krb5_pam_handler(struct be_req *be_req); + +#endif /* __KRB5_AUTH_H__ */ diff --git a/src/providers/krb5/krb5_become_user.c b/src/providers/krb5/krb5_become_user.c new file mode 100644 index 00000000..351f539a --- /dev/null +++ b/src/providers/krb5/krb5_become_user.c @@ -0,0 +1,61 @@ +/* + SSSD + + Kerberos 5 Backend Module -- Utilities + + Authors: + Sumit Bose + + 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 . +*/ + +#include +#include + +#include "util/util.h" + +errno_t become_user(uid_t uid, gid_t gid) +{ + int ret; + + DEBUG(9, ("Trying to become user [%d][%d].\n", uid, gid)); + ret = setgid(gid); + if (ret == -1) { + DEBUG(1, ("setgid failed [%d][%s].\n", errno, strerror(errno))); + return errno; + } + + ret = setuid(uid); + if (ret == -1) { + DEBUG(1, ("setuid failed [%d][%s].\n", errno, strerror(errno))); + return errno; + } + + ret = setegid(gid); + if (ret == -1) { + DEBUG(1, ("setegid failed [%d][%s].\n", errno, strerror(errno))); + return errno; + } + + ret = seteuid(uid); + if (ret == -1) { + DEBUG(1, ("seteuid failed [%d][%s].\n", errno, strerror(errno))); + return errno; + } + + return EOK; +} + diff --git a/src/providers/krb5/krb5_child.c b/src/providers/krb5/krb5_child.c new file mode 100644 index 00000000..5e185940 --- /dev/null +++ b/src/providers/krb5/krb5_child.c @@ -0,0 +1,1030 @@ +/* + SSSD + + Kerberos 5 Backend Module -- tgt_req and changepw child + + Authors: + Sumit Bose + + 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 . +*/ + +#include +#include +#include +#include + +#include + +#include "util/util.h" +#include "util/user_info_msg.h" +#include "providers/child_common.h" +#include "providers/dp_backend.h" +#include "providers/krb5/krb5_auth.h" +#include "providers/krb5/krb5_utils.h" + +struct krb5_child_ctx { + /* opts taken from kinit */ + /* in seconds */ + krb5_deltat starttime; + krb5_deltat lifetime; + krb5_deltat rlife; + + int forwardable; + int proxiable; + int addresses; + + int not_forwardable; + int not_proxiable; + int no_addresses; + + int verbose; + + char* principal_name; + char* service_name; + char* keytab_name; + char* k5_cache_name; + char* k4_cache_name; + + action_type action; + + char *kdcip; + char *realm; + char *changepw_principle; + char *ccache_dir; + char *ccname_template; + int auth_timeout; + + int child_debug_fd; +}; + +struct krb5_req { + krb5_context ctx; + krb5_principal princ; + char* name; + krb5_creds *creds; + krb5_get_init_creds_opt *options; + pid_t child_pid; + int read_from_child_fd; + int write_to_child_fd; + + struct be_req *req; + struct pam_data *pd; + struct krb5_child_ctx *krb5_ctx; + errno_t (*child_req)(int fd, struct krb5_req *kr); + + char *ccname; + char *keytab; + bool validate; +}; + +static krb5_context krb5_error_ctx; +static const char *__krb5_error_msg; +#define KRB5_DEBUG(level, krb5_error) do { \ + __krb5_error_msg = sss_krb5_get_error_message(krb5_error_ctx, krb5_error); \ + DEBUG(level, ("%d: [%d][%s]\n", __LINE__, krb5_error, __krb5_error_msg)); \ + sss_krb5_free_error_message(krb5_error_ctx, __krb5_error_msg); \ +} while(0); + +static krb5_error_code create_empty_cred(struct krb5_req *kr, krb5_creds **_cred) +{ + krb5_error_code kerr; + krb5_creds *cred = NULL; + krb5_data *krb5_realm; + + cred = calloc(sizeof(krb5_creds), 1); + if (cred == NULL) { + DEBUG(1, ("calloc failed.\n")); + return ENOMEM; + } + + kerr = krb5_copy_principal(kr->ctx, kr->princ, &cred->client); + if (kerr != 0) { + DEBUG(1, ("krb5_copy_principal failed.\n")); + goto done; + } + + krb5_realm = krb5_princ_realm(kr->ctx, kr->princ); + + kerr = krb5_build_principal_ext(kr->ctx, &cred->server, + krb5_realm->length, krb5_realm->data, + KRB5_TGS_NAME_SIZE, KRB5_TGS_NAME, + krb5_realm->length, krb5_realm->data, 0); + if (kerr != 0) { + DEBUG(1, ("krb5_build_principal_ext failed.\n")); + goto done; + } + +done: + if (kerr != 0) { + if (cred != NULL && cred->client != NULL) { + krb5_free_principal(kr->ctx, cred->client); + } + + free(cred); + } else { + *_cred = cred; + } + + return kerr; +} + +static krb5_error_code create_ccache_file(struct krb5_req *kr, krb5_creds *creds) +{ + krb5_error_code kerr; + krb5_ccache tmp_cc = NULL; + char *cc_file_name; + int fd = -1; + size_t ccname_len; + char *dummy; + char *tmp_ccname; + krb5_creds *l_cred; + + if (strncmp(kr->ccname, "FILE:", 5) == 0) { + cc_file_name = kr->ccname + 5; + } else { + cc_file_name = kr->ccname; + } + + if (cc_file_name[0] != '/') { + DEBUG(1, ("Ccache filename is not an absolute path.\n")); + return EINVAL; + } + + dummy = strrchr(cc_file_name, '/'); + tmp_ccname = talloc_strndup(kr, cc_file_name, (size_t) (dummy-cc_file_name)); + if (tmp_ccname == NULL) { + DEBUG(1, ("talloc_strdup failed.\n")); + return ENOMEM; + } + tmp_ccname = talloc_asprintf_append(tmp_ccname, "/.krb5cc_dummy_XXXXXX"); + + fd = mkstemp(tmp_ccname); + if (fd == -1) { + DEBUG(1, ("mkstemp failed [%d][%s].\n", errno, strerror(errno))); + return errno; + } + + kerr = krb5_cc_resolve(kr->ctx, tmp_ccname, &tmp_cc); + if (kerr != 0) { + KRB5_DEBUG(1, kerr); + goto done; + } + + kerr = krb5_cc_initialize(kr->ctx, tmp_cc, kr->princ); + if (kerr != 0) { + KRB5_DEBUG(1, kerr); + goto done; + } + if (fd != -1) { + close(fd); + fd = -1; + } + + if (creds == NULL) { + kerr = create_empty_cred(kr, &l_cred); + if (kerr != 0) { + KRB5_DEBUG(1, kerr); + goto done; + } + } else { + l_cred = creds; + } + + kerr = krb5_cc_store_cred(kr->ctx, tmp_cc, l_cred); + if (kerr != 0) { + KRB5_DEBUG(1, kerr); + goto done; + } + + kerr = krb5_cc_close(kr->ctx, tmp_cc); + if (kerr != 0) { + KRB5_DEBUG(1, kerr); + goto done; + } + tmp_cc = NULL; + + ccname_len = strlen(cc_file_name); + if (ccname_len >= 6 && strcmp(cc_file_name + (ccname_len-6), "XXXXXX")==0 ) { + fd = mkstemp(cc_file_name); + if (fd == -1) { + DEBUG(1, ("mkstemp failed [%d][%s].\n", errno, strerror(errno))); + kerr = errno; + goto done; + } + } + + kerr = rename(tmp_ccname, cc_file_name); + if (kerr == -1) { + DEBUG(1, ("rename failed [%d][%s].\n", errno, strerror(errno))); + } + +done: + if (fd != -1) { + close(fd); + fd = -1; + } + if (kerr != 0 && tmp_cc != NULL) { + krb5_cc_destroy(kr->ctx, tmp_cc); + } + return kerr; +} + +static struct response *init_response(TALLOC_CTX *mem_ctx) { + struct response *r; + r = talloc(mem_ctx, struct response); + r->buf = talloc_size(mem_ctx, MAX_CHILD_MSG_SIZE); + if (r->buf == NULL) { + DEBUG(1, ("talloc_size failed.\n")); + return NULL; + } + r->max_size = MAX_CHILD_MSG_SIZE; + r->size = 0; + + return r; +} + +static errno_t pack_response_packet(struct response *resp, int status, int type, + size_t len, const uint8_t *data) +{ + int p=0; + + if ((3*sizeof(int32_t) + len +1) > resp->max_size) { + DEBUG(1, ("response message too big.\n")); + return ENOMEM; + } + + COPY_INT32_VALUE(&resp->buf[p], status, p); + COPY_INT32_VALUE(&resp->buf[p], type, p); + COPY_INT32_VALUE(&resp->buf[p], len, p); + COPY_MEM(&resp->buf[p], data, p, len); + + resp->size = p; + + return EOK; +} + +static struct response *prepare_response_message(struct krb5_req *kr, + krb5_error_code kerr, + char *user_error_message, + int pam_status) +{ + char *msg = NULL; + const char *krb5_msg = NULL; + int ret; + struct response *resp; + size_t user_resp_len; + uint8_t *user_resp; + + resp = init_response(kr); + if (resp == NULL) { + DEBUG(1, ("init_response failed.\n")); + return NULL; + } + + if (kerr == 0) { + if(kr->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) { + ret = pack_response_packet(resp, PAM_SUCCESS, SSS_PAM_SYSTEM_INFO, + strlen("success") + 1, + (const uint8_t *) "success"); + } else { + if (kr->ccname == NULL) { + DEBUG(1, ("Error obtaining ccname.\n")); + return NULL; + } + + msg = talloc_asprintf(kr, "%s=%s",CCACHE_ENV_NAME, kr->ccname); + if (msg == NULL) { + DEBUG(1, ("talloc_asprintf failed.\n")); + return NULL; + } + + ret = pack_response_packet(resp, PAM_SUCCESS, SSS_PAM_ENV_ITEM, + strlen(msg) + 1, (uint8_t *) msg); + talloc_zfree(msg); + } + } else { + + if (user_error_message != NULL) { + ret = pack_user_info_chpass_error(kr, user_error_message, + &user_resp_len, &user_resp); + if (ret != EOK) { + DEBUG(1, ("pack_user_info_chpass_error failed.\n")); + talloc_zfree(user_error_message); + } else { + ret = pack_response_packet(resp, pam_status, SSS_PAM_USER_INFO, + user_resp_len, user_resp); + if (ret != EOK) { + DEBUG(1, ("pack_response_packet failed.\n")); + talloc_zfree(user_error_message); + } + } + } + + if (user_error_message == NULL) { + krb5_msg = sss_krb5_get_error_message(krb5_error_ctx, kerr); + if (krb5_msg == NULL) { + DEBUG(1, ("sss_krb5_get_error_message failed.\n")); + return NULL; + } + + ret = pack_response_packet(resp, pam_status, SSS_PAM_SYSTEM_INFO, + strlen(krb5_msg) + 1, + (const uint8_t *) krb5_msg); + sss_krb5_free_error_message(krb5_error_ctx, krb5_msg); + } else { + + } + + } + + if (ret != EOK) { + DEBUG(1, ("pack_response_packet failed.\n")); + return NULL; + } + + return resp; +} + +static errno_t sendresponse(int fd, krb5_error_code kerr, + char *user_error_message, int pam_status, + struct krb5_req *kr) +{ + struct response *resp; + size_t written; + int ret; + + resp = prepare_response_message(kr, kerr, user_error_message, pam_status); + if (resp == NULL) { + DEBUG(1, ("prepare_response_message failed.\n")); + return ENOMEM; + } + + written = 0; + while (written < resp->size) { + ret = write(fd, resp->buf + written, resp->size - written); + if (ret == -1) { + if (errno == EAGAIN || errno == EINTR) { + continue; + } + ret = errno; + DEBUG(1, ("write failed [%d][%s].\n", ret, strerror(ret))); + return ret; + } + written += ret; + } + + return EOK; +} + +static krb5_error_code validate_tgt(struct krb5_req *kr) +{ + krb5_error_code kerr; + krb5_error_code kt_err; + char *principal; + krb5_keytab keytab; + krb5_kt_cursor cursor; + krb5_keytab_entry entry; + krb5_verify_init_creds_opt opt; + + memset(&keytab, 0, sizeof(keytab)); + kerr = krb5_kt_resolve(kr->ctx, kr->keytab, &keytab); + if (kerr != 0) { + DEBUG(1, ("error resolving keytab [%s], not verifying TGT.\n", + kr->keytab)); + return kerr; + } + + memset(&cursor, 0, sizeof(cursor)); + kerr = krb5_kt_start_seq_get(kr->ctx, keytab, &cursor); + if (kerr != 0) { + DEBUG(1, ("error reading keytab [%s], not verifying TGT.\n", + kr->keytab)); + return kerr; + } + + /* We look for the first entry from our realm or take the last one */ + memset(&entry, 0, sizeof(entry)); + while ((kt_err = krb5_kt_next_entry(kr->ctx, keytab, &entry, &cursor)) == 0) { + if (krb5_realm_compare(kr->ctx, entry.principal, kr->princ)) { + DEBUG(9, ("Found keytab entry with the realm of the credential.\n")); + break; + } + + kerr = krb5_free_keytab_entry_contents(kr->ctx, &entry); + if (kerr != 0) { + DEBUG(1, ("Failed to free keytab entry.\n")); + } + memset(&entry, 0, sizeof(entry)); + } + + /* Close the keytab here. Even though we're using cursors, the file + * handle is stored in the krb5_keytab structure, and it gets + * overwritten when the verify_init_creds() call below creates its own + * cursor, creating a leak. */ + kerr = krb5_kt_end_seq_get(kr->ctx, keytab, &cursor); + if (kerr != 0) { + DEBUG(1, ("krb5_kt_end_seq_get failed, not verifying TGT.\n")); + goto done; + } + + /* check if we got any errors from krb5_kt_next_entry */ + if (kt_err != 0 && kt_err != KRB5_KT_END) { + DEBUG(1, ("error reading keytab [%s], not verifying TGT.\n", + kr->keytab)); + goto done; + } + + /* Get the principal to which the key belongs, for logging purposes. */ + principal = NULL; + kerr = krb5_unparse_name(kr->ctx, entry.principal, &principal); + if (kerr != 0) { + DEBUG(1, ("internal error parsing principal name, " + "not verifying TGT.\n")); + goto done; + } + + + krb5_verify_init_creds_opt_init(&opt); + kerr = krb5_verify_init_creds(kr->ctx, kr->creds, entry.principal, keytab, + NULL, &opt); + + if (kerr == 0) { + DEBUG(5, ("TGT verified using key for [%s].\n", principal)); + } else { + DEBUG(1 ,("TGT failed verification using key for [%s].\n", principal)); + } + +done: + if (krb5_kt_close(kr->ctx, keytab) != 0) { + DEBUG(1, ("krb5_kt_close failed")); + } + if (krb5_free_keytab_entry_contents(kr->ctx, &entry) != 0) { + DEBUG(1, ("Failed to free keytab entry.\n")); + } + if (principal != NULL) { + sss_krb5_free_unparsed_name(kr->ctx, principal); + } + + return kerr; + +} + +static krb5_error_code get_and_save_tgt(struct krb5_req *kr, + char *password) +{ + krb5_error_code kerr = 0; + int ret; + + kerr = krb5_get_init_creds_password(kr->ctx, kr->creds, kr->princ, + password, NULL, NULL, 0, NULL, + kr->options); + if (kerr != 0) { + KRB5_DEBUG(1, kerr); + return kerr; + } + + if (kr->validate) { + kerr = validate_tgt(kr); + if (kerr != 0) { + KRB5_DEBUG(1, kerr); + return kerr; + } + + /* We drop root privileges which were needed to read the keytab file + * for the validation validation of the credentials here to run the + * ccache I/O operations with user privileges. */ + ret = become_user(kr->pd->pw_uid, kr->pd->gr_gid); + if (ret != EOK) { + DEBUG(1, ("become_user failed.\n")); + return ret; + } + } else { + DEBUG(9, ("TGT validation is disabled.\n")); + } + + kerr = create_ccache_file(kr, kr->creds); + if (kerr != 0) { + KRB5_DEBUG(1, kerr); + goto done; + } + + kerr = 0; + +done: + krb5_free_cred_contents(kr->ctx, kr->creds); + + return kerr; + +} + +static errno_t changepw_child(int fd, struct krb5_req *kr) +{ + int ret; + krb5_error_code kerr = 0; + char *pass_str = NULL; + char *newpass_str = NULL; + int pam_status = PAM_SYSTEM_ERR; + int result_code = -1; + krb5_data result_code_string; + krb5_data result_string; + char *user_error_message = NULL; + + pass_str = talloc_strndup(kr, (const char *) kr->pd->authtok, + kr->pd->authtok_size); + if (pass_str == NULL) { + DEBUG(1, ("talloc_strndup failed.\n")); + kerr = KRB5KRB_ERR_GENERIC; + goto sendresponse; + } + + kerr = krb5_get_init_creds_password(kr->ctx, kr->creds, kr->princ, + pass_str, NULL, NULL, 0, + kr->krb5_ctx->changepw_principle, + kr->options); + if (kerr != 0) { + KRB5_DEBUG(1, kerr); + if (kerr == KRB5_KDC_UNREACH) { + pam_status = PAM_AUTHINFO_UNAVAIL; + } + goto sendresponse; + } + + memset(pass_str, 0, kr->pd->authtok_size); + talloc_zfree(pass_str); + memset(kr->pd->authtok, 0, kr->pd->authtok_size); + + if (kr->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) { + DEBUG(9, ("Initial authentication for change password operation " + "successfull.\n")); + krb5_free_cred_contents(kr->ctx, kr->creds); + pam_status = PAM_SUCCESS; + goto sendresponse; + } + + newpass_str = talloc_strndup(kr, (const char *) kr->pd->newauthtok, + kr->pd->newauthtok_size); + if (newpass_str == NULL) { + DEBUG(1, ("talloc_strndup failed.\n")); + kerr = KRB5KRB_ERR_GENERIC; + goto sendresponse; + } + + kerr = krb5_change_password(kr->ctx, kr->creds, newpass_str, &result_code, + &result_code_string, &result_string); + + if (kerr != 0 || result_code != 0) { + if (kerr != 0) { + KRB5_DEBUG(1, kerr); + } else { + kerr = KRB5KRB_ERR_GENERIC; + } + + if (result_code_string.length > 0) { + DEBUG(1, ("krb5_change_password failed [%d][%.*s].\n", result_code, + result_code_string.length, result_code_string.data)); + user_error_message = talloc_strndup(kr->pd, result_code_string.data, + result_code_string.length); + if (user_error_message == NULL) { + DEBUG(1, ("talloc_strndup failed.\n")); + } + } + + if (result_string.length > 0) { + DEBUG(1, ("krb5_change_password failed [%d][%.*s].\n", result_code, + result_string.length, result_string.data)); + talloc_free(user_error_message); + user_error_message = talloc_strndup(kr->pd, result_string.data, + result_string.length); + if (user_error_message == NULL) { + DEBUG(1, ("talloc_strndup failed.\n")); + } + } + + pam_status = PAM_AUTHTOK_ERR; + goto sendresponse; + } + + krb5_free_cred_contents(kr->ctx, kr->creds); + + kerr = get_and_save_tgt(kr, newpass_str); + memset(newpass_str, 0, kr->pd->newauthtok_size); + talloc_zfree(newpass_str); + memset(kr->pd->newauthtok, 0, kr->pd->newauthtok_size); + + if (kerr != 0) { + KRB5_DEBUG(1, kerr); + if (kerr == KRB5_KDC_UNREACH) { + pam_status = PAM_AUTHINFO_UNAVAIL; + } + } + +sendresponse: + ret = sendresponse(fd, kerr, user_error_message, pam_status, kr); + if (ret != EOK) { + DEBUG(1, ("sendresponse failed.\n")); + } + + return ret; +} + +static errno_t tgt_req_child(int fd, struct krb5_req *kr) +{ + int ret; + krb5_error_code kerr = 0; + char *pass_str = NULL; + int pam_status = PAM_SYSTEM_ERR; + + pass_str = talloc_strndup(kr, (const char *) kr->pd->authtok, + kr->pd->authtok_size); + if (pass_str == NULL) { + DEBUG(1, ("talloc_strndup failed.\n")); + kerr = KRB5KRB_ERR_GENERIC; + goto sendresponse; + } + + kerr = get_and_save_tgt(kr, pass_str); + + /* If the password is expired the KDC will always return + KRB5KDC_ERR_KEY_EXP regardless if the supplied password is correct or + not. In general the password can still be used to get a changepw ticket. + So we validate the password by trying to get a changepw ticket. */ + if (kerr == KRB5KDC_ERR_KEY_EXP) { + kerr = krb5_get_init_creds_password(kr->ctx, kr->creds, kr->princ, + pass_str, NULL, NULL, 0, + kr->krb5_ctx->changepw_principle, + kr->options); + krb5_free_cred_contents(kr->ctx, kr->creds); + if (kerr == 0) { + kerr = KRB5KDC_ERR_KEY_EXP; + } + } + + memset(pass_str, 0, kr->pd->authtok_size); + talloc_zfree(pass_str); + memset(kr->pd->authtok, 0, kr->pd->authtok_size); + + if (kerr != 0) { + KRB5_DEBUG(1, kerr); + switch (kerr) { + case KRB5_KDC_UNREACH: + pam_status = PAM_AUTHINFO_UNAVAIL; + break; + case KRB5KDC_ERR_KEY_EXP: + pam_status = PAM_AUTHTOK_EXPIRED; + break; + case KRB5KDC_ERR_PREAUTH_FAILED: + pam_status = PAM_CRED_ERR; + break; + default: + pam_status = PAM_SYSTEM_ERR; + } + } + +sendresponse: + ret = sendresponse(fd, kerr, NULL, pam_status, kr); + if (ret != EOK) { + DEBUG(1, ("sendresponse failed.\n")); + } + + return ret; +} + +static errno_t create_empty_ccache(int fd, struct krb5_req *kr) +{ + int ret; + int pam_status = PAM_SUCCESS; + + ret = create_ccache_file(kr, NULL); + if (ret != 0) { + KRB5_DEBUG(1, ret); + pam_status = PAM_SYSTEM_ERR; + } + + ret = sendresponse(fd, ret, NULL, pam_status, kr); + if (ret != EOK) { + DEBUG(1, ("sendresponse failed.\n")); + } + + return ret; +} + +static errno_t unpack_buffer(uint8_t *buf, size_t size, struct pam_data *pd, + char **ccname, char **keytab, uint32_t *validate, + uint32_t *offline) +{ + size_t p = 0; + uint32_t len; + + COPY_UINT32_CHECK(&pd->cmd, buf + p, p, size); + COPY_UINT32_CHECK(&pd->pw_uid, buf + p, p, size); + COPY_UINT32_CHECK(&pd->gr_gid, buf + p, p, size); + COPY_UINT32_CHECK(validate, buf + p, p, size); + COPY_UINT32_CHECK(offline, buf + p, p, size); + + COPY_UINT32_CHECK(&len, buf + p, p, size); + if ((p + len ) > size) return EINVAL; + pd->upn = talloc_strndup(pd, (char *)(buf + p), len); + if (pd->upn == NULL) return ENOMEM; + p += len; + + COPY_UINT32_CHECK(&len, buf + p, p, size); + if ((p + len ) > size) return EINVAL; + *ccname = talloc_strndup(pd, (char *)(buf + p), len); + if (*ccname == NULL) return ENOMEM; + p += len; + + COPY_UINT32_CHECK(&len, buf + p, p, size); + if ((p + len ) > size) return EINVAL; + *keytab = talloc_strndup(pd, (char *)(buf + p), len); + if (*keytab == NULL) return ENOMEM; + p += len; + + COPY_UINT32_CHECK(&len, buf + p, p, size); + if ((p + len) > size) return EINVAL; + pd->authtok = (uint8_t *)talloc_strndup(pd, (char *)(buf + p), len); + if (pd->authtok == NULL) return ENOMEM; + pd->authtok_size = len + 1; + p += len; + + if (pd->cmd == SSS_PAM_CHAUTHTOK) { + COPY_UINT32_CHECK(&len, buf + p, p, size); + + if ((p + len) > size) return EINVAL; + pd->newauthtok = (uint8_t *)talloc_strndup(pd, (char *)(buf + p), len); + if (pd->newauthtok == NULL) return ENOMEM; + pd->newauthtok_size = len + 1; + p += len; + } else { + pd->newauthtok = NULL; + pd->newauthtok_size = 0; + } + + return EOK; +} + +static int krb5_cleanup(void *ptr) +{ + struct krb5_req *kr = talloc_get_type(ptr, struct krb5_req); + if (kr == NULL) return EOK; + + if (kr->options != NULL) { + sss_krb5_get_init_creds_opt_free(kr->ctx, kr->options); + } + + if (kr->creds != NULL) { + krb5_free_cred_contents(kr->ctx, kr->creds); + krb5_free_creds(kr->ctx, kr->creds); + } + if (kr->name != NULL) + sss_krb5_free_unparsed_name(kr->ctx, kr->name); + if (kr->princ != NULL) + krb5_free_principal(kr->ctx, kr->princ); + if (kr->ctx != NULL) + krb5_free_context(kr->ctx); + + if (kr->krb5_ctx != NULL) { + memset(kr->krb5_ctx, 0, sizeof(struct krb5_child_ctx)); + } + memset(kr, 0, sizeof(struct krb5_req)); + + return EOK; +} + +static int krb5_setup(struct pam_data *pd, const char *user_princ_str, + uint32_t offline, struct krb5_req **krb5_req) +{ + struct krb5_req *kr = NULL; + krb5_error_code kerr = 0; + + kr = talloc_zero(pd, struct krb5_req); + if (kr == NULL) { + DEBUG(1, ("talloc failed.\n")); + kerr = ENOMEM; + goto failed; + } + talloc_set_destructor((TALLOC_CTX *) kr, krb5_cleanup); + + kr->krb5_ctx = talloc_zero(kr, struct krb5_child_ctx); + if (kr->krb5_ctx == NULL) { + DEBUG(1, ("talloc failed.\n")); + kerr = ENOMEM; + goto failed; + } + + kr->krb5_ctx->changepw_principle = getenv(SSSD_KRB5_CHANGEPW_PRINCIPLE); + if (kr->krb5_ctx->changepw_principle == NULL) { + DEBUG(1, ("Cannot read [%s] from environment.\n", + SSSD_KRB5_CHANGEPW_PRINCIPLE)); + if (pd->cmd == SSS_PAM_CHAUTHTOK) { + goto failed; + } + } + + kr->krb5_ctx->realm = getenv(SSSD_KRB5_REALM); + if (kr->krb5_ctx->realm == NULL) { + DEBUG(2, ("Cannot read [%s] from environment.\n", SSSD_KRB5_REALM)); + } + + kr->pd = pd; + + switch(pd->cmd) { + case SSS_PAM_AUTHENTICATE: + /* If we are offline, we need to create an empty ccache file */ + if (offline) { + kr->child_req = create_empty_ccache; + } else { + kr->child_req = tgt_req_child; + } + break; + case SSS_PAM_CHAUTHTOK: + case SSS_PAM_CHAUTHTOK_PRELIM: + kr->child_req = changepw_child; + break; + default: + DEBUG(1, ("PAM command [%d] not supported.\n", pd->cmd)); + kerr = EINVAL; + goto failed; + } + + kerr = krb5_init_context(&kr->ctx); + if (kerr != 0) { + KRB5_DEBUG(1, kerr); + goto failed; + } + + kerr = krb5_parse_name(kr->ctx, user_princ_str, &kr->princ); + if (kerr != 0) { + KRB5_DEBUG(1, kerr); + goto failed; + } + + kerr = krb5_unparse_name(kr->ctx, kr->princ, &kr->name); + if (kerr != 0) { + KRB5_DEBUG(1, kerr); + goto failed; + } + + kr->creds = calloc(1, sizeof(krb5_creds)); + if (kr->creds == NULL) { + DEBUG(1, ("talloc_zero failed.\n")); + kerr = ENOMEM; + goto failed; + } + + kerr = sss_krb5_get_init_creds_opt_alloc(kr->ctx, &kr->options); + if (kerr != 0) { + KRB5_DEBUG(1, kerr); + goto failed; + } + +/* TODO: set options, e.g. + * krb5_get_init_creds_opt_set_tkt_life + * krb5_get_init_creds_opt_set_renew_life + * krb5_get_init_creds_opt_set_forwardable + * krb5_get_init_creds_opt_set_proxiable + * krb5_get_init_creds_opt_set_etype_list + * krb5_get_init_creds_opt_set_address_list + * krb5_get_init_creds_opt_set_preauth_list + * krb5_get_init_creds_opt_set_salt + * krb5_get_init_creds_opt_set_change_password_prompt + * krb5_get_init_creds_opt_set_pa + */ + + *krb5_req = kr; + return EOK; + +failed: + talloc_free(kr); + + return kerr; +} + +int main(int argc, const char *argv[]) +{ + uint8_t *buf = NULL; + int ret; + ssize_t len = 0; + struct pam_data *pd = NULL; + struct krb5_req *kr = NULL; + char *ccname; + char *keytab; + uint32_t validate; + uint32_t offline; + int opt; + poptContext pc; + int debug_fd = -1; + + struct poptOption long_options[] = { + POPT_AUTOHELP + {"debug-level", 'd', POPT_ARG_INT, &debug_level, 0, + _("Debug level"), NULL}, + {"debug-timestamps", 0, POPT_ARG_INT, &debug_timestamps, 0, + _("Add debug timestamps"), NULL}, + {"debug-fd", 0, POPT_ARG_INT, &debug_fd, 0, + _("An open file descriptor for the debug logs"), NULL}, + POPT_TABLEEND + }; + + + 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); + _exit(-1); + } + } + + poptFreeContext(pc); + + DEBUG(7, ("krb5_child started.\n")); + + pd = talloc(NULL, struct pam_data); + if (pd == NULL) { + DEBUG(1, ("malloc failed.\n")); + _exit(-1); + } + + debug_prg_name = talloc_asprintf(pd, "[sssd[krb5_child[%d]]]", getpid()); + + if (debug_fd != -1) { + ret = set_debug_file_from_fd(debug_fd); + if (ret != EOK) { + DEBUG(1, ("set_debug_file_from_fd failed.\n")); + } + } + + buf = talloc_size(pd, sizeof(uint8_t)*IN_BUF_SIZE); + if (buf == NULL) { + DEBUG(1, ("malloc failed.\n")); + _exit(-1); + } + + while ((ret = read(STDIN_FILENO, buf + len, IN_BUF_SIZE - len)) != 0) { + if (ret == -1) { + if (errno == EINTR || errno == EAGAIN) { + continue; + } + DEBUG(1, ("read failed [%d][%s].\n", errno, strerror(errno))); + goto fail; + } else if (ret > 0) { + len += ret; + if (len > IN_BUF_SIZE) { + DEBUG(1, ("read too much, this should never happen.\n")); + goto fail; + } + continue; + } else { + DEBUG(1, ("unexpected return code of read [%d].\n", ret)); + goto fail; + } + } + close(STDIN_FILENO); + + ret = unpack_buffer(buf, len, pd, &ccname, &keytab, &validate, &offline); + if (ret != EOK) { + DEBUG(1, ("unpack_buffer failed.\n")); + goto fail; + } + + ret = krb5_setup(pd, pd->upn, offline, &kr); + if (ret != EOK) { + DEBUG(1, ("krb5_setup failed.\n")); + goto fail; + } + kr->ccname = ccname; + kr->keytab = keytab; + kr->validate = (validate == 0) ? false : true; + + ret = kr->child_req(STDOUT_FILENO, kr); + if (ret != EOK) { + DEBUG(1, ("Child request failed.\n")); + goto fail; + } + + close(STDOUT_FILENO); + talloc_free(pd); + + return 0; + +fail: + close(STDOUT_FILENO); + talloc_free(pd); + exit(-1); +} diff --git a/src/providers/krb5/krb5_common.c b/src/providers/krb5/krb5_common.c new file mode 100644 index 00000000..86676f44 --- /dev/null +++ b/src/providers/krb5/krb5_common.c @@ -0,0 +1,356 @@ +/* + SSSD + + Kerberos Provider Common Functions + + Authors: + Sumit Bose + + Copyright (C) 2008-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 . +*/ +#include +#include +#include +#include + +#include "providers/dp_backend.h" +#include "providers/krb5/krb5_common.h" + +struct dp_option default_krb5_opts[] = { + { "krb5_kdcip", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_realm", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_ccachedir", DP_OPT_STRING, { "/tmp" }, NULL_STRING }, + { "krb5_ccname_template", DP_OPT_STRING, { "FILE:%d/krb5cc_%U_XXXXXX" }, NULL_STRING}, + { "krb5_changepw_principal", DP_OPT_STRING, { "kadmin/changepw" }, NULL_STRING }, + { "krb5_auth_timeout", DP_OPT_NUMBER, { .number = 15 }, NULL_NUMBER }, + { "krb5_keytab", DP_OPT_STRING, { "/etc/krb5.keytab" }, NULL_STRING }, + { "krb5_validate", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE } +}; + +errno_t check_and_export_options(struct dp_option *opts, + struct sss_domain_info *dom) +{ + int ret; + char *value; + const char *realm; + const char *dummy; + struct stat stat_buf; + char **list; + + realm = dp_opt_get_cstring(opts, KRB5_REALM); + if (realm == NULL) { + ret = dp_opt_set_string(opts, KRB5_REALM, dom->name); + if (ret != EOK) { + DEBUG(1, ("dp_opt_set_string failed.\n")); + return ret; + } + realm = dom->name; + } + + ret = setenv(SSSD_KRB5_REALM, realm, 1); + if (ret != EOK) { + DEBUG(2, ("setenv %s failed, authentication might fail.\n", + SSSD_KRB5_REALM)); + } + + dummy = dp_opt_get_cstring(opts, KRB5_KDC); + if (dummy == NULL) { + DEBUG(1, ("No KDC expicitly configured, using defaults")); + } else { + ret = split_on_separator(opts, dummy, ',', true, &list, NULL); + if (ret != EOK) { + DEBUG(1, ("Failed to parse server list!\n")); + return ret; + } + ret = write_kdcinfo_file(realm, list[0]); + if (ret != EOK) { + DEBUG(1, ("write_kdcinfo_file failed, " + "using kerberos defaults from /etc/krb5.conf")); + } + talloc_free(list); + } + + dummy = dp_opt_get_cstring(opts, KRB5_CCACHEDIR); + ret = lstat(dummy, &stat_buf); + if (ret != EOK) { + DEBUG(1, ("lstat for [%s] failed: [%d][%s].\n", dummy, errno, + strerror(errno))); + return ret; + } + if ( !S_ISDIR(stat_buf.st_mode) ) { + DEBUG(1, ("Value of krb5ccache_dir [%s] is not a directory.\n", dummy)); + return EINVAL; + } + + dummy = dp_opt_get_cstring(opts, KRB5_CCNAME_TMPL); + if (dummy == NULL) { + DEBUG(1, ("Missing credential cache name template.\n")); + return EINVAL; + } + if (dummy[0] != '/' && strncmp(dummy, "FILE:", 5) != 0) { + DEBUG(1, ("Currently only file based credential caches are supported " + "and krb5ccname_template must start with '/' or 'FILE:'\n")); + return EINVAL; + } + + dummy = dp_opt_get_cstring(opts, KRB5_CHANGEPW_PRINC); + if (dummy == NULL) { + DEBUG(1, ("Missing change password principle.\n")); + return EINVAL; + } + if (strchr(dummy, '@') == NULL) { + value = talloc_asprintf(opts, "%s@%s", dummy, realm); + if (value == NULL) { + DEBUG(7, ("talloc_asprintf failed.\n")); + return ENOMEM; + } + ret = dp_opt_set_string(opts, KRB5_CHANGEPW_PRINC, value); + if (ret != EOK) { + DEBUG(1, ("dp_opt_set_string failed.\n")); + return ret; + } + dummy = value; + } + + ret = setenv(SSSD_KRB5_CHANGEPW_PRINCIPLE, dummy, 1); + if (ret != EOK) { + DEBUG(2, ("setenv %s failed, password change might fail.\n", + SSSD_KRB5_CHANGEPW_PRINCIPLE)); + } + + return EOK; +} + +errno_t krb5_get_options(TALLOC_CTX *memctx, struct confdb_ctx *cdb, + const char *conf_path, struct dp_option **_opts) +{ + int ret; + struct dp_option *opts; + + opts = talloc_zero(memctx, struct dp_option); + if (opts == NULL) { + DEBUG(1, ("talloc_zero failed.\n")); + return ENOMEM; + } + + ret = dp_get_options(opts, cdb, conf_path, default_krb5_opts, + KRB5_OPTS, &opts); + if (ret != EOK) { + DEBUG(1, ("dp_get_options failed.\n")); + goto done; + } + + *_opts = opts; + ret = EOK; + +done: + if (ret != EOK) { + talloc_zfree(opts); + } + + return ret; +} + +errno_t write_kdcinfo_file(const char *realm, const char *kdc) +{ + int ret; + int fd = -1; + char *tmp_name = NULL; + char *kdcinfo_name = NULL; + TALLOC_CTX *tmp_ctx = NULL; + int kdc_len; + + if (realm == NULL || *realm == '\0' || kdc == NULL || *kdc == '\0') { + DEBUG(1, ("Missing or empty realm or kdc.\n")); + return EINVAL; + } + + kdc_len = strlen(kdc); + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(1, ("talloc_new failed.\n")); + return ENOMEM; + } + + tmp_name = talloc_asprintf(tmp_ctx, PUBCONF_PATH"/.kdcinfo_dummy_XXXXXX"); + if (tmp_name == NULL) { + DEBUG(1, ("talloc_asprintf failed.\n")); + ret = ENOMEM; + goto done; + } + + kdcinfo_name = talloc_asprintf(tmp_ctx, KDCINFO_TMPL, realm); + if (kdcinfo_name == NULL) { + DEBUG(1, ("talloc_asprintf failed.\n")); + ret = ENOMEM; + goto done; + } + + fd = mkstemp(tmp_name); + if (fd == -1) { + DEBUG(1, ("mkstemp failed [%d][%s].\n", errno, strerror(errno))); + ret = errno; + goto done; + } + + ret = write(fd, kdc, kdc_len); + if (ret == -1) { + DEBUG(1, ("write failed [%d][%s].\n", errno, strerror(errno))); + goto done; + } + if (ret != kdc_len) { + DEBUG(1, ("Partial write occured, this should never happen.\n")); + ret = EINTR; + goto done; + } + + ret = fchmod(fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); + if (ret == -1) { + DEBUG(1, ("fchmod failed [%d][%s].\n", errno, strerror(errno))); + goto done; + } + + ret = close(fd); + if (ret == -1) { + DEBUG(1, ("close failed [%d][%s].\n", errno, strerror(errno))); + goto done; + } + + ret = rename(tmp_name, kdcinfo_name); + if (ret == -1) { + DEBUG(1, ("rename failed [%d][%s].\n", errno, strerror(errno))); + goto done; + } + +done: + talloc_free(tmp_ctx); + return ret; +} + +static void krb5_resolve_callback(void *private_data, struct fo_server *server) +{ + struct krb5_service *krb5_service; + struct hostent *srvaddr; + char *address; + int ret; + + krb5_service = talloc_get_type(private_data, struct krb5_service); + if (!krb5_service) { + DEBUG(1, ("FATAL: Bad private_data\n")); + return; + } + + srvaddr = fo_get_server_hostent(server); + if (!srvaddr) { + DEBUG(1, ("FATAL: No hostent available for server (%s)\n", + fo_get_server_name(server))); + return; + } + + address = talloc_asprintf(krb5_service, "%s", srvaddr->h_name); + if (!address) { + DEBUG(1, ("Failed to copy address ...\n")); + return; + } + + talloc_zfree(krb5_service->address); + krb5_service->address = address; + + ret = write_kdcinfo_file(krb5_service->realm, address); + if (ret != EOK) { + DEBUG(2, ("write_kdcinfo_file failed, authentication might fail.\n")); + } + + return; +} + + +int krb5_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx, + const char *service_name, const char *servers, + const char *realm, struct krb5_service **_service) +{ + TALLOC_CTX *tmp_ctx; + struct krb5_service *service; + char **list = NULL; + int ret; + int i; + + tmp_ctx = talloc_new(memctx); + if (!tmp_ctx) { + return ENOMEM; + } + + service = talloc_zero(tmp_ctx, struct krb5_service); + if (!service) { + ret = ENOMEM; + goto done; + } + + ret = be_fo_add_service(ctx, service_name); + if (ret != EOK) { + DEBUG(1, ("Failed to create failover service!\n")); + goto done; + } + + service->name = talloc_strdup(service, service_name); + if (!service->name) { + ret = ENOMEM; + goto done; + } + + service->realm = talloc_strdup(service, realm); + if (!service->realm) { + ret = ENOMEM; + goto done; + } + + ret = split_on_separator(tmp_ctx, servers, ',', true, &list, NULL); + if (ret != EOK) { + DEBUG(1, ("Failed to parse server list!\n")); + goto done; + } + + for (i = 0; list[i]; i++) { + + talloc_steal(service, list[i]); + + ret = be_fo_add_server(ctx, service_name, list[i], 0, NULL); + if (ret && ret != EEXIST) { + DEBUG(0, ("Failed to add server\n")); + goto done; + } + + DEBUG(6, ("Added Server %s\n", list[i])); + } + + ret = be_fo_service_add_callback(memctx, ctx, service_name, + krb5_resolve_callback, service); + if (ret != EOK) { + DEBUG(1, ("Failed to add failover callback!\n")); + goto done; + } + + ret = EOK; + +done: + if (ret == EOK) { + *_service = talloc_steal(memctx, service); + } + talloc_zfree(tmp_ctx); + return ret; +} + diff --git a/src/providers/krb5/krb5_common.h b/src/providers/krb5/krb5_common.h new file mode 100644 index 00000000..832ffcdd --- /dev/null +++ b/src/providers/krb5/krb5_common.h @@ -0,0 +1,72 @@ +/* + SSSD + + Kerberos Backend, common header file + + Authors: + Sumit Bose + + 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 . +*/ + +#ifndef __KRB5_COMMON_H__ +#define __KRB5_COMMON_H__ + +#include "config.h" +#include + +#include "providers/dp_backend.h" +#include "util/util.h" +#include "util/sss_krb5.h" + +#define SSSD_KRB5_KDC "SSSD_KRB5_KDC" +#define SSSD_KRB5_REALM "SSSD_KRB5_REALM" +#define SSSD_KRB5_CHANGEPW_PRINCIPLE "SSSD_KRB5_CHANGEPW_PRINCIPLE" + +#define KDCINFO_TMPL PUBCONF_PATH"/kdcinfo.%s" + +enum krb5_opts { + KRB5_KDC = 0, + KRB5_REALM, + KRB5_CCACHEDIR, + KRB5_CCNAME_TMPL, + KRB5_CHANGEPW_PRINC, + KRB5_AUTH_TIMEOUT, + KRB5_KEYTAB, + KRB5_VALIDATE, + + KRB5_OPTS +}; + +struct krb5_service { + char *name; + char *address; + char *realm; +}; + +errno_t check_and_export_options(struct dp_option *opts, + struct sss_domain_info *dom); + +errno_t krb5_get_options(TALLOC_CTX *memctx, struct confdb_ctx *cdb, + const char *conf_path, struct dp_option **_opts); + +errno_t write_kdcinfo_file(const char *realm, const char *kdc); + +int krb5_service_init(TALLOC_CTX *memctx, struct be_ctx *ctx, + const char *service_name, const char *servers, + const char *realm, struct krb5_service **_service); +#endif /* __KRB5_COMMON_H__ */ diff --git a/src/providers/krb5/krb5_init.c b/src/providers/krb5/krb5_init.c new file mode 100644 index 00000000..43cbc1bc --- /dev/null +++ b/src/providers/krb5/krb5_init.c @@ -0,0 +1,152 @@ +/* + SSSD + + Kerberos 5 Backend Module + + Authors: + Sumit Bose + + 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 . +*/ + +#include +#include +#include +#include +#include "providers/child_common.h" +#include "providers/krb5/krb5_auth.h" +#include "providers/krb5/krb5_common.h" + +struct krb5_options { + struct dp_option *opts; + struct krb5_ctx *auth_ctx; +}; + +struct krb5_options *krb5_options = NULL; + +struct bet_ops krb5_auth_ops = { + .handler = krb5_pam_handler, + .finalize = NULL, +}; + +int sssm_krb5_auth_init(struct be_ctx *bectx, + struct bet_ops **ops, + void **pvt_auth_data) +{ + struct krb5_ctx *ctx = NULL; + int ret; + struct tevent_signal *sige; + unsigned v; + FILE *debug_filep; + const char *krb5_servers; + const char *krb5_realm; + + if (krb5_options == NULL) { + krb5_options = talloc_zero(bectx, struct krb5_options); + if (krb5_options == NULL) { + DEBUG(1, ("talloc_zero failed.\n")); + return ENOMEM; + } + ret = krb5_get_options(krb5_options, bectx->cdb, bectx->conf_path, + &krb5_options->opts); + if (ret != EOK) { + DEBUG(1, ("krb5_get_options failed.\n")); + return ret; + } + } + + if (krb5_options->auth_ctx != NULL) { + *ops = &krb5_auth_ops; + *pvt_auth_data = krb5_options->auth_ctx; + return EOK; + } + + ctx = talloc_zero(bectx, struct krb5_ctx); + if (!ctx) { + DEBUG(1, ("talloc failed.\n")); + return ENOMEM; + } + + ctx->action = INIT_PW; + ctx->opts = krb5_options->opts; + + krb5_servers = dp_opt_get_string(ctx->opts, KRB5_KDC); + if (krb5_servers == NULL) { + DEBUG(0, ("Missing krb5_kdcip option!\n")); + return EINVAL; + } + + krb5_realm = dp_opt_get_string(ctx->opts, KRB5_REALM); + if (krb5_realm == NULL) { + DEBUG(0, ("Missing krb5_realm option!\n")); + return EINVAL; + } + + ret = krb5_service_init(ctx, bectx, "KRB5", krb5_servers, krb5_realm, + &ctx->service); + if (ret != EOK) { + DEBUG(0, ("Failed to init IPA failover service!\n")); + return ret; + } + + ret = check_and_export_options(ctx->opts, bectx->domain); + if (ret != EOK) { + DEBUG(1, ("check_and_export_options failed.\n")); + goto fail; + } + + sige = tevent_add_signal(bectx->ev, ctx, SIGCHLD, SA_SIGINFO, + child_sig_handler, NULL); + if (sige == NULL) { + DEBUG(1, ("tevent_add_signal failed.\n")); + ret = ENOMEM; + goto fail; + } + + if (debug_to_file != 0) { + ret = open_debug_file_ex("krb5_child", &debug_filep); + if (ret != EOK) { + DEBUG(0, ("Error setting up logging (%d) [%s]\n", + ret, strerror(ret))); + goto fail; + } + + ctx->child_debug_fd = fileno(debug_filep); + if (ctx->child_debug_fd == -1) { + DEBUG(0, ("fileno failed [%d][%s]\n", errno, strerror(errno))); + ret = errno; + goto fail; + } + + v = fcntl(ctx->child_debug_fd, F_GETFD, 0); + fcntl(ctx->child_debug_fd, F_SETFD, v & ~FD_CLOEXEC); + } + + *ops = &krb5_auth_ops; + *pvt_auth_data = ctx; + return EOK; + +fail: + talloc_free(ctx); + return ret; +} + +int sssm_krb5_chpass_init(struct be_ctx *bectx, + struct bet_ops **ops, + void **pvt_auth_data) +{ + return sssm_krb5_auth_init(bectx, ops, pvt_auth_data); +} diff --git a/src/providers/krb5/krb5_utils.c b/src/providers/krb5/krb5_utils.c new file mode 100644 index 00000000..489030af --- /dev/null +++ b/src/providers/krb5/krb5_utils.c @@ -0,0 +1,145 @@ +/* + SSSD + + Kerberos 5 Backend Module -- Utilities + + Authors: + Sumit Bose + + 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 . +*/ +#include +#include + +#include "providers/krb5/krb5_utils.h" +#include "providers/krb5/krb5_auth.h" +#include "util/util.h" + +char *expand_ccname_template(TALLOC_CTX *mem_ctx, struct krb5child_req *kr, + const char *template) +{ + char *copy; + char *p; + char *n; + char *result = NULL; + const char *dummy; + + if (template == NULL) { + DEBUG(1, ("Missing template.\n")); + return NULL; + } + + copy = talloc_strdup(mem_ctx, template); + if (copy == NULL) { + DEBUG(1, ("talloc_strdup failed.\n")); + return NULL; + } + + result = talloc_strdup(mem_ctx, ""); + if (result == NULL) { + DEBUG(1, ("talloc_strdup failed.\n")); + return NULL; + } + + p = copy; + while ( (n = strchr(p, '%')) != NULL) { + *n = '\0'; + n++; + if ( *n == '\0' ) { + DEBUG(1, ("format error, single %% at the end of the template.\n")); + return NULL; + } + + switch( *n ) { + case 'u': + if (kr->pd->user == NULL) { + DEBUG(1, ("Cannot expand user name template " + "because user name is empty.\n")); + return NULL; + } + result = talloc_asprintf_append(result, "%s%s", p, + kr->pd->user); + break; + case 'U': + if (kr->pd->pw_uid <= 0) { + DEBUG(1, ("Cannot expand uid template " + "because uid is invalid.\n")); + return NULL; + } + result = talloc_asprintf_append(result, "%s%d", p, + kr->pd->pw_uid); + break; + case 'p': + if (kr->pd->upn == NULL) { + DEBUG(1, ("Cannot expand user principle name template " + "because upn is empty.\n")); + return NULL; + } + result = talloc_asprintf_append(result, "%s%s", p, kr->pd->upn); + break; + case '%': + result = talloc_asprintf_append(result, "%s%%", p); + break; + case 'r': + dummy = dp_opt_get_string(kr->krb5_ctx->opts, KRB5_REALM); + if (dummy == NULL) { + DEBUG(1, ("Missing kerberos realm.\n")); + return NULL; + } + result = talloc_asprintf_append(result, "%s%s", p, dummy); + break; + case 'h': + if (kr->homedir == NULL) { + DEBUG(1, ("Cannot expand home directory template " + "because the path is not available.\n")); + return NULL; + } + result = talloc_asprintf_append(result, "%s%s", p, kr->homedir); + break; + case 'd': + dummy = dp_opt_get_string(kr->krb5_ctx->opts, KRB5_CCACHEDIR); + if (dummy == NULL) { + DEBUG(1, ("Missing credential cache directory.\n")); + return NULL; + } + result = talloc_asprintf_append(result, "%s%s", p, dummy); + break; + case 'P': + if (kr->pd->cli_pid == 0) { + DEBUG(1, ("Cannot expand PID template " + "because PID is not available.\n")); + return NULL; + } + result = talloc_asprintf_append(result, "%s%d", p, + kr->pd->cli_pid); + break; + default: + DEBUG(1, ("format error, unknown template [%%%c].\n", *n)); + return NULL; + } + + if (result == NULL) { + DEBUG(1, ("talloc_asprintf_append failed.\n")); + return NULL; + } + + p = n + 1; + } + + result = talloc_asprintf_append(result, "%s", p); + + return result; +} diff --git a/src/providers/krb5/krb5_utils.h b/src/providers/krb5/krb5_utils.h new file mode 100644 index 00000000..7637041a --- /dev/null +++ b/src/providers/krb5/krb5_utils.h @@ -0,0 +1,39 @@ +/* + SSSD + + Kerberos Backend, header file for utilities + + Authors: + Sumit Bose + + 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 . +*/ + +#ifndef __KRB5_UTILS_H__ +#define __KRB5_UTILS_H__ + +#include + +#include "providers/krb5/krb5_auth.h" +#include "providers/data_provider.h" + +char *expand_ccname_template(TALLOC_CTX *mem_ctx, struct krb5child_req *kr, + const char *template); + +errno_t become_user(uid_t uid, gid_t gid); + +#endif /* __KRB5_UTILS_H__ */ -- cgit