summaryrefslogtreecommitdiffstats
path: root/src/providers/krb5
diff options
context:
space:
mode:
Diffstat (limited to 'src/providers/krb5')
-rw-r--r--src/providers/krb5/krb5_auth.c1193
-rw-r--r--src/providers/krb5/krb5_auth.h91
-rw-r--r--src/providers/krb5/krb5_become_user.c61
-rw-r--r--src/providers/krb5/krb5_child.c1030
-rw-r--r--src/providers/krb5/krb5_common.c356
-rw-r--r--src/providers/krb5/krb5_common.h72
-rw-r--r--src/providers/krb5/krb5_init.c152
-rw-r--r--src/providers/krb5/krb5_utils.c145
-rw-r--r--src/providers/krb5/krb5_utils.h39
9 files changed, 3139 insertions, 0 deletions
diff --git a/src/providers/krb5/krb5_auth.c b/src/providers/krb5/krb5_auth.c
new file mode 100644
index 000000000..a2dadc808
--- /dev/null
+++ b/src/providers/krb5/krb5_auth.c
@@ -0,0 +1,1193 @@
+/*
+ SSSD
+
+ Kerberos 5 Backend Module
+
+ Authors:
+ 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/>.
+*/
+
+#include <errno.h>
+#include <sys/time.h>
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <pwd.h>
+#include <sys/stat.h>
+
+#include <security/pam_modules.h>
+
+#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 000000000..a011af890
--- /dev/null
+++ b/src/providers/krb5/krb5_auth.h
@@ -0,0 +1,91 @@
+/*
+ SSSD
+
+ Kerberos Backend, private header file
+
+ Authors:
+ 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 __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 000000000..351f539a3
--- /dev/null
+++ b/src/providers/krb5/krb5_become_user.c
@@ -0,0 +1,61 @@
+/*
+ SSSD
+
+ Kerberos 5 Backend Module -- Utilities
+
+ Authors:
+ 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/>.
+*/
+
+#include <sys/types.h>
+#include <unistd.h>
+
+#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 000000000..5e185940e
--- /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 <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/>.
+*/
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <popt.h>
+
+#include <security/pam_modules.h>
+
+#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 000000000..86676f44c
--- /dev/null
+++ b/src/providers/krb5/krb5_common.c
@@ -0,0 +1,356 @@
+/*
+ SSSD
+
+ Kerberos Provider Common Functions
+
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <netdb.h>
+
+#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 000000000..832ffcdd5
--- /dev/null
+++ b/src/providers/krb5/krb5_common.h
@@ -0,0 +1,72 @@
+/*
+ SSSD
+
+ Kerberos Backend, common header file
+
+ Authors:
+ 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 __KRB5_COMMON_H__
+#define __KRB5_COMMON_H__
+
+#include "config.h"
+#include <stdbool.h>
+
+#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 000000000..43cbc1bc7
--- /dev/null
+++ b/src/providers/krb5/krb5_init.c
@@ -0,0 +1,152 @@
+/*
+ SSSD
+
+ Kerberos 5 Backend Module
+
+ Authors:
+ 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/>.
+*/
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#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 000000000..489030af8
--- /dev/null
+++ b/src/providers/krb5/krb5_utils.c
@@ -0,0 +1,145 @@
+/*
+ SSSD
+
+ Kerberos 5 Backend Module -- Utilities
+
+ Authors:
+ 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/>.
+*/
+#include <string.h>
+#include <stdlib.h>
+
+#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 000000000..7637041a6
--- /dev/null
+++ b/src/providers/krb5/krb5_utils.h
@@ -0,0 +1,39 @@
+/*
+ SSSD
+
+ Kerberos Backend, header file for utilities
+
+ Authors:
+ 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 __KRB5_UTILS_H__
+#define __KRB5_UTILS_H__
+
+#include <talloc.h>
+
+#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__ */