diff options
Diffstat (limited to '')
-rw-r--r-- | src/tests/krb5_child-test.c | 525 |
1 files changed, 525 insertions, 0 deletions
diff --git a/src/tests/krb5_child-test.c b/src/tests/krb5_child-test.c new file mode 100644 index 0000000..ceed041 --- /dev/null +++ b/src/tests/krb5_child-test.c @@ -0,0 +1,525 @@ +/* + 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" + +/* Interfaces being tested */ +#include "providers/krb5/krb5_auth.h" +#include "providers/krb5/krb5_common.h" +#include "providers/krb5/krb5_utils.h" +#include "providers/krb5/krb5_ccache.h" + +extern struct dp_option default_krb5_opts[]; + +static krb5_context krb5_error_ctx; +#define KRB5_CHILD_TEST_DEBUG(level, error) KRB5_DEBUG(level, krb5_error_ctx, error) + +#define CHECK_KRET_L(kret, err, label) do { \ + if (kret) { \ + KRB5_CHILD_TEST_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\n"); + talloc_free(ctx); + return EFAULT; + } + + *_ctx = ctx; + return EOK; +} + +static struct krb5_ctx * +create_dummy_krb5_ctx(TALLOC_CTX *mem_ctx, const char *realm) +{ + struct krb5_ctx *krb5_ctx; + int i; + errno_t ret; + + krb5_ctx = talloc_zero(mem_ctx, struct krb5_ctx); + if (!krb5_ctx) return NULL; + + ret = sss_regexp_new(krb5_ctx, ILLEGAL_PATH_PATTERN, 0, &(krb5_ctx->illegal_path_re)); + if (ret != EOK) { + goto fail; + } + + /* 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; + const char *authtok; + size_t authtok_len; + errno_t ret; + + pd = create_pam_data(mem_ctx); + if (!pd) goto fail; + + pd->cmd = SSS_PAM_AUTHENTICATE; + pd->user = talloc_strdup(pd, user); + if (!pd->user) goto fail; + + ret = sss_authtok_set_password(pd->authtok, password, 0); + if (ret) goto fail; + + (void)sss_authtok_get_password(pd->authtok, &authtok, &authtok_len); + DEBUG(SSSDBG_FUNC_DATA, "Authtok [%s] len [%d]\n", + authtok, (int)authtok_len); + + 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; + 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); + if (!kr->krb5_ctx) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to create dummy krb5_ctx\n"); + goto fail; + } + /* PAM Data structure */ + kr->pd = create_dummy_pam_data(kr, user, password); + if (!kr->pd) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Failed to create dummy pam_data"); + goto fail; + } + + ret = krb5_get_simple_upn(kr, kr->krb5_ctx, NULL, kr->pd->user, NULL, + &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) { + ret = dp_opt_set_int(kr->krb5_ctx->opts, KRB5_AUTH_TIMEOUT, timeout); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to set value for krb5_auth_timeout\n"); + goto fail; + } + } + + if (!ccname) { + kr->ccname = expand_ccname_template(kr, kr, + dp_opt_get_cstring(kr->krb5_ctx->opts, + KRB5_CCNAME_TMPL), + kr->krb5_ctx->illegal_path_re, true, true); + if (!kr->ccname) goto fail; + + DEBUG(SSSDBG_FUNC_DATA, "ccname [%s] uid [%llu] gid [%llu]\n", + kr->ccname, (unsigned long long) kr->uid, + (unsigned long long) kr->gid); + } else { + kr->ccname = talloc_strdup(kr, ccname); + } + if (!kr->ccname) goto fail; + + DEBUG(SSSDBG_FUNC_DATA, "ccname [%s] uid [%u] gid [%u]\n", + kr->ccname, kr->uid, kr->gid); + + ret = sss_krb5_precreate_ccache(kr->ccname, + kr->uid, kr->gid); + 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'; + +#ifdef HAVE_KRB5_TIMESTAMP_TO_SFSTRING + kret = krb5_timestamp_to_sfstring(ts, timestring, BUFSIZ, &fill); + if (kret) { + KRB5_CHILD_TEST_DEBUG(SSSDBG_OP_FAILURE, kret); + } + printf("%s", timestring); +#else + printf("%s", ctime(&ts)); +#endif /* HAVE_KRB5_TIMESTAMP_TO_SFSTRING */ +} + +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 = EIO; + + 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); + } + + kret = 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; +} + +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_CLI_INIT(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 + && ctx->kr) { + sss_krb5_cc_destroy(ctx->res->ccname, ctx->kr->uid, ctx->kr->gid); + } + free(password); + talloc_free(ctx); + poptFreeContext(pc); + return ret; +} |