diff options
-rw-r--r-- | Makefile.am | 24 | ||||
-rw-r--r-- | src/tests/krb5_child-test.c | 551 |
2 files changed, 575 insertions, 0 deletions
diff --git a/Makefile.am b/Makefile.am index 8af213e5..7bdc5930 100644 --- a/Makefile.am +++ b/Makefile.am @@ -120,6 +120,7 @@ endif check_PROGRAMS = \ stress-tests \ + krb5-child-test \ $(non_interactive_check_based_tests) PYTHON_TESTS = @@ -972,6 +973,29 @@ stress_tests_LDADD = \ libsss_util.la \ libsss_test_common.la +krb5_child_test_SOURCES = \ + src/tests/krb5_child-test.c \ + src/providers/krb5/krb5_utils.c \ + src/providers/krb5/krb5_child_handler.c \ + src/providers/krb5/krb5_become_user.c \ + src/providers/krb5/krb5_common.c \ + src/util/sss_krb5.c \ + src/providers/data_provider_fo.c \ + src/providers/data_provider_opts.c \ + src/providers/data_provider_callbacks.c \ + $(SSSD_FAILOVER_OBJ) +krb5_child_test_CFLAGS = \ + $(AM_CFLAGS) \ + -DKRB5_CHILD_DIR=\"$(builddir)\" \ + $(CHECK_CFLAGS) +krb5_child_test_LDADD = \ + $(SSSD_LIBS)\ + $(CARES_LIBS) \ + $(KRB5_LIBS) \ + $(CHECK_LIBS) \ + libsss_util.la \ + libsss_test_common.la + noinst_PROGRAMS = pam_test_client if BUILD_SUDO noinst_PROGRAMS += sss_sudo_cli diff --git a/src/tests/krb5_child-test.c b/src/tests/krb5_child-test.c new file mode 100644 index 00000000..4dde996a --- /dev/null +++ b/src/tests/krb5_child-test.c @@ -0,0 +1,551 @@ +/* + SSSD + + Unit tests - exercise the krb5 child + + Authors: + Jakub Hrozek <jhrozek@redhat.com> + + Copyright (C) 2012 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 <stdio.h> +#include <stdlib.h> +#include <talloc.h> +#include <popt.h> +#include <errno.h> +#include <unistd.h> +#include <limits.h> + +#include "util/util.h" +#include "src/tools/tools_util.h" + +/* Interfaces being tested */ +#include "providers/krb5/krb5_auth.h" +#include "providers/krb5/krb5_common.h" +#include "providers/krb5/krb5_utils.h" + +extern struct dp_option default_krb5_opts[]; + +static krb5_context krb5_error_ctx; +#define KRB5_DEBUG(level, krb5_error) do { \ + const char * __krb5_error_msg; \ + __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_log(SSS_LOG_ERR, "%s", __krb5_error_msg); \ + sss_krb5_free_error_message(krb5_error_ctx, __krb5_error_msg); \ +} while(0) + +#define CHECK_KRET(kret, err) do { \ + if (kret) { \ + KRB5_DEBUG(SSSDBG_OP_FAILURE, kret); \ + return err; \ + } \ +} while(0) \ + +#define CHECK_KRET_L(kret, err, label) do { \ + if (kret) { \ + KRB5_DEBUG(SSSDBG_OP_FAILURE, kret); \ + goto label; \ + } \ +} while(0) \ + +struct krb5_child_test_ctx { + struct tevent_context *ev; + struct krb5child_req *kr; + + bool done; + errno_t child_ret; + + uint8_t *buf; + ssize_t len; + struct krb5_child_response *res; +}; + +static errno_t +setup_krb5_child_test(TALLOC_CTX *mem_ctx, struct krb5_child_test_ctx **_ctx) +{ + struct krb5_child_test_ctx *ctx; + + ctx = talloc_zero(mem_ctx, struct krb5_child_test_ctx); + if (!ctx) return ENOMEM; + + ctx->ev = tevent_context_init(ctx); + if (ctx->ev == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, ("Could not init tevent context")); + talloc_free(ctx); + return EFAULT; + } + + *_ctx = ctx; + return EOK; +} + +int re_destructor(void *memctx) +{ + struct krb5_ctx *ctx = (struct krb5_ctx *) memctx; + + if (ctx->illegal_path_re) { + pcre_free(ctx->illegal_path_re); + ctx->illegal_path_re = NULL; + } + return 0; +} + +static struct krb5_ctx * +create_dummy_krb5_ctx(TALLOC_CTX *mem_ctx, const char *realm) +{ + struct krb5_ctx *krb5_ctx; + const char *errstr; + int errval; + int errpos; + int i; + errno_t ret; + + krb5_ctx = talloc_zero(mem_ctx, struct krb5_ctx); + if (!krb5_ctx) return NULL; + + krb5_ctx->illegal_path_re = pcre_compile2(ILLEGAL_PATH_PATTERN, 0, + &errval, &errstr, &errpos, NULL); + if (krb5_ctx->illegal_path_re == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + ("Invalid Regular Expression pattern at position %d. " + "(Error: %d [%s])\n", errpos, errval, errstr)); + goto fail; + } + talloc_set_destructor((TALLOC_CTX *) krb5_ctx, re_destructor); + + /* Kerberos options */ + krb5_ctx->opts = talloc_zero_array(krb5_ctx, struct dp_option, KRB5_OPTS); + if (!krb5_ctx->opts) goto fail; + for (i = 0; i < KRB5_OPTS; i++) { + krb5_ctx->opts[i].opt_name = default_krb5_opts[i].opt_name; + krb5_ctx->opts[i].type = default_krb5_opts[i].type; + krb5_ctx->opts[i].def_val = default_krb5_opts[i].def_val; + switch (krb5_ctx->opts[i].type) { + case DP_OPT_STRING: + ret = dp_opt_set_string(krb5_ctx->opts, i, + default_krb5_opts[i].def_val.string); + break; + case DP_OPT_BLOB: + ret = dp_opt_set_blob(krb5_ctx->opts, i, + default_krb5_opts[i].def_val.blob); + break; + case DP_OPT_NUMBER: + ret = dp_opt_set_int(krb5_ctx->opts, i, + default_krb5_opts[i].def_val.number); + break; + case DP_OPT_BOOL: + ret = dp_opt_set_bool(krb5_ctx->opts, i, + default_krb5_opts[i].def_val.boolean); + break; + } + if (ret) goto fail; + } + + ret = dp_opt_set_string(krb5_ctx->opts, KRB5_REALM, realm); + if (ret) goto fail; + + return krb5_ctx; + +fail: + talloc_free(krb5_ctx); + return NULL; +} + +static struct pam_data * +create_dummy_pam_data(TALLOC_CTX *mem_ctx, const char *user, + const char *password) +{ + struct pam_data *pd; + + pd = talloc_zero(mem_ctx, struct pam_data); + if (!pd) goto fail; + + pd->cmd = SSS_PAM_AUTHENTICATE; + pd->user = talloc_strdup(pd, user); + if (!pd->user) goto fail; + + pd->authtok = discard_const(talloc_strdup(pd, password)); + if (!pd->authtok) goto fail; + pd->authtok_size = strlen(password); + pd->authtok_type = SSS_AUTHTOK_TYPE_PASSWORD; + DEBUG(SSSDBG_FUNC_DATA, ("Authtok [%s] len [%d]\n", + pd->authtok, pd->authtok_size)); + + return pd; + +fail: + talloc_free(pd); + return NULL; +} + +static struct krb5child_req * +create_dummy_req(TALLOC_CTX *mem_ctx, const char *user, + const char *password, const char *realm, + const char *ccname, const char *ccname_template, + int timeout) +{ + struct krb5child_req *kr; + struct passwd *pwd; + bool private; + errno_t ret; + + /* The top level child request */ + kr = talloc_zero(mem_ctx, struct krb5child_req); + if (!kr) return NULL; + + pwd = getpwnam(user); + if (!pwd) { + DEBUG(SSSDBG_FATAL_FAILURE, + ("Cannot get info on user [%s]\n", user)); + goto fail; + } + + kr->uid = pwd->pw_uid; + kr->gid = pwd->pw_gid; + + /* The Kerberos context */ + kr->krb5_ctx = create_dummy_krb5_ctx(kr, realm); + /* PAM Data structure */ + kr->pd = create_dummy_pam_data(kr, user, password); + + ret = krb5_get_simple_upn(kr, kr->krb5_ctx, kr->pd->user, &kr->upn); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, ("krb5_get_simple_upn failed.\n")); + goto fail; + } + + /* Override options with what was provided by the user */ + if (ccname_template) { + ret = dp_opt_set_string(kr->krb5_ctx->opts, KRB5_CCNAME_TMPL, + ccname_template); + if (ret != EOK) goto fail; + } + + if (timeout) { + dp_opt_set_int(kr->krb5_ctx->opts, KRB5_AUTH_TIMEOUT, timeout); + } + + if (!ccname) { + kr->ccname = expand_ccname_template(kr, kr, + dp_opt_get_cstring(kr->krb5_ctx->opts, + KRB5_CCNAME_TMPL), + true, true, &private); + if (!kr->ccname) goto fail; + + DEBUG(SSSDBG_FUNC_DATA, ("ccname [%s] uid [%llu] gid [%llu]\n", + kr->ccname, kr->uid, kr->gid)); + + ret = create_ccache_dir(kr, kr->ccname, + kr->krb5_ctx->illegal_path_re, + kr->uid, kr->gid, private); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, ("create_ccache_dir failed.\n")); + goto fail; + } + } + + return kr; + +fail: + talloc_free(kr); + return NULL; +} + +static void +child_done(struct tevent_req *req) +{ + struct krb5_child_test_ctx *ctx = tevent_req_callback_data(req, + struct krb5_child_test_ctx); + errno_t ret; + + ret = handle_child_recv(req, ctx, &ctx->buf, &ctx->len); + talloc_free(req); + ctx->done = true; + ctx->child_ret = ret; +} + +static void +printtime(krb5_timestamp ts) +{ + krb5_error_code kret; + char timestring[BUFSIZ]; + char fill = '\0'; + + kret = krb5_timestamp_to_sfstring(ts, timestring, BUFSIZ, &fill); + if (kret) { + KRB5_DEBUG(SSSDBG_OP_FAILURE, kret); + } + printf("%s", timestring); +} + +static void +print_creds(krb5_context kcontext, krb5_creds *cred, const char *defname) +{ + krb5_error_code kret; + char *name = NULL; + char *sname = NULL; + + kret = krb5_unparse_name(kcontext, cred->client, &name); + CHECK_KRET_L(kret, EIO, done); + + kret = krb5_unparse_name(kcontext, cred->server, &sname); + CHECK_KRET_L(kret, EIO, done); + + if (!cred->times.starttime) { + cred->times.starttime = cred->times.authtime; + } + + + printf("\t\t%s\n", sname); + printf("\t\tValid from\t"); printtime(cred->times.starttime); + printf("\n\t\tValid until\t"); printtime(cred->times.endtime); + printf("\n"); + + if (strcmp(name, defname)) { + printf("\t\tfor client %s", name); + } + +done: + krb5_free_unparsed_name(kcontext, name); + krb5_free_unparsed_name(kcontext, sname); +} + +static errno_t +print_ccache(const char *cc) +{ + krb5_cc_cursor cur; + krb5_ccache cache = NULL; + krb5_error_code kret; + krb5_context kcontext = NULL; + krb5_principal_data *princ = NULL; + krb5_creds creds; + char *defname = NULL; + int i = 1; + errno_t ret; + + kret = krb5_init_context(&kcontext); + CHECK_KRET_L(kret, EIO, done); + + kret = krb5_cc_resolve(kcontext, cc, &cache); + CHECK_KRET_L(kret, EIO, done); + + kret = krb5_cc_get_principal(kcontext, cache, &princ); + CHECK_KRET_L(kret, EIO, done); + + kret = krb5_unparse_name(kcontext, princ, &defname); + CHECK_KRET_L(kret, EIO, done); + + printf("\nTicket cache: %s:%s\nDefault principal: %s\n\n", + krb5_cc_get_type(kcontext, cache), + krb5_cc_get_name(kcontext, cache), defname); + + kret = krb5_cc_start_seq_get(kcontext, cache, &cur); + CHECK_KRET_L(kret, EIO, done); + + while (!(kret = krb5_cc_next_cred(kcontext, cache, &cur, &creds))) { + printf("Ticket #%d:\n", i); + print_creds(kcontext, &creds, defname); + krb5_free_cred_contents(kcontext, &creds); + } + + krb5_cc_end_seq_get(kcontext, cache, &cur); + CHECK_KRET_L(kret, EIO, done); + + ret = EOK; +done: + krb5_cc_close(kcontext, cache); + krb5_free_unparsed_name(kcontext, defname); + krb5_free_principal(kcontext, princ); + krb5_free_context(kcontext); + return ret; +} + +static void +remove_ccache(const char *cc) +{ + size_t offset = 0; + errno_t ret; + + if (strncmp(cc, "FILE:", 5) == 0) { + offset = 5; + } + if (cc[offset] != '/') { + DEBUG(SSSDBG_FATAL_FAILURE, + ("ccname [%s] does not contain absolute path?\n", cc)); + } + + errno = 0; + ret = unlink(cc+offset); + if (ret == -1) { + ret = errno; + DEBUG(SSSDBG_FATAL_FAILURE, + ("unlink [%s] failed [%d]: %s\n", cc, ret, strerror(ret))); + } +} + +int +main(int argc, const char *argv[]) +{ + int opt; + errno_t ret; + struct krb5_child_test_ctx *ctx = NULL; + struct tevent_req *req; + + int pc_debug = 0; + int pc_timeout = 0; + const char *pc_user = NULL;; + const char *pc_passwd = NULL;; + const char *pc_realm = NULL;; + const char *pc_ccname = NULL;; + const char *pc_ccname_tp = NULL;; + char *password = NULL; + bool rm_ccache = true; + + poptContext pc; + struct poptOption long_options[] = { + POPT_AUTOHELP + { "debug", '\0', POPT_ARG_INT | POPT_ARGFLAG_DOC_HIDDEN, &pc_debug, 0, + "The debug level to run with", NULL }, + { "user", 'u', POPT_ARG_STRING, &pc_user, 0, + "The user to log in as", NULL }, + { "password", 'w', POPT_ARG_STRING, &pc_passwd, 0, + "The authtok to use", NULL }, + { "ask-password", 'W', POPT_ARG_NONE, NULL, 'W', + "Ask interactively for authtok", NULL }, + { "ccname", 'c', POPT_ARG_STRING, &pc_ccname, 0, + "Force usage of a certain credential cache", NULL }, + { "ccname-template", 't', POPT_ARG_STRING, &pc_ccname_tp, 0, + "Specify the credential cache template", NULL }, + { "realm", 'r', POPT_ARG_STRING, &pc_realm, 0, + "The Kerberos realm to use", NULL }, + { "keep-ccache", 'k', POPT_ARG_NONE, NULL, 'k', + "Do not delete the ccache when the tool finishes", NULL }, + { "timeout", '\0', POPT_ARG_INT, &pc_timeout, 0, + "The timeout for the child, in seconds", NULL }, + POPT_TABLEEND + }; + + debug_prg_name = argv[0]; + pc = poptGetContext(NULL, argc, argv, long_options, 0); + + while ((opt = poptGetNextOpt(pc)) > 0) { + switch(opt) { + case 'W': + errno = 0; + password = getpass("Enter password:"); + if (!password) { + return 1; + } + break; + case 'k': + rm_ccache = false; + break; + default: + DEBUG(SSSDBG_FATAL_FAILURE, ("Unexpected option\n")); + return 1; + } + } + + debug_level = debug_convert_old_level(pc_debug); + + if (opt != -1) { + poptPrintUsage(pc, stderr, 0); + fprintf(stderr, "%s", poptStrerror(opt)); + return 1; + } + + if (!pc_user) { + DEBUG(SSSDBG_FATAL_FAILURE, ("Please specify the user\n")); + poptPrintUsage(pc, stderr, 0); + return 1; + } + + if (!pc_realm) { + DEBUG(SSSDBG_FATAL_FAILURE, ("Please specify the realm\n")); + poptPrintUsage(pc, stderr, 0); + return 1; + } + + if (!password && !pc_passwd) { + DEBUG(SSSDBG_FATAL_FAILURE, + ("Password was not provided or asked for\n")); + poptPrintUsage(pc, stderr, 0); + return 1; + } + + if (pc_ccname && pc_ccname_tp) { + DEBUG(SSSDBG_MINOR_FAILURE, + ("Both ccname and ccname template specified, " + "will prefer ccname\n")); + } + + ret = setup_krb5_child_test(NULL, &ctx); + if (ret != EOK) { + poptPrintUsage(pc, stderr, 0); + fprintf(stderr, "%s", poptStrerror(opt)); + return 3; + } + + ctx->kr = create_dummy_req(ctx, pc_user, password ? password : pc_passwd, + pc_realm, pc_ccname, pc_ccname_tp, pc_timeout); + if (!ctx->kr) { + DEBUG(SSSDBG_FATAL_FAILURE, ("Cannot create Kerberos request\n")); + ret = 4; + goto done; + } + + req = handle_child_send(ctx, ctx->ev, ctx->kr); + if (!req) { + DEBUG(SSSDBG_FATAL_FAILURE, ("Cannot create child request\n")); + ret = 4; + goto done; + } + tevent_req_set_callback(req, child_done, ctx); + + while (ctx->done == false) { + tevent_loop_once(ctx->ev); + } + + printf("Child returned %d\n", ctx->child_ret); + + ret = parse_krb5_child_response(ctx, ctx->buf, ctx->len, + ctx->kr->pd, 0, &ctx->res); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, ("Could not parse child response\n")); + ret = 5; + goto done; + } + + if (!ctx->res->ccname) { + fprintf(stderr, "No ccname returned\n"); + ret = 6; + goto done; + } + + print_ccache(ctx->res->ccname); + + ret = 0; +done: + if (rm_ccache && ctx->res && ctx->res->ccname) { + remove_ccache(ctx->res->ccname); + } + free(password); + talloc_free(ctx); + poptFreeContext(pc); + return ret; +} |