/* SSSD Unit tests - exercise the krb5 child Authors: Jakub Hrozek 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 . */ #include #include #include #include #include #include #include #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[]; extern struct sss_krb5_cc_be file_cc; extern struct sss_krb5_cc_be dir_cc; static krb5_context krb5_error_ctx; #define KRB5_CHILD_TEST_DEBUG(level, error) KRB5_DEBUG(level, krb5_error_ctx, error) #define CHECK_KRET(kret, err) do { \ if (kret) { \ KRB5_CHILD_TEST_DEBUG(SSSDBG_OP_FAILURE, kret); \ return err; \ } \ } while(0) \ #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")); 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) { enum sss_krb5_cc_type cc_be; struct krb5child_req *kr; struct passwd *pwd; bool private; errno_t ret; const char *tmpl; /* 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; tmpl = ccname_template; } else { tmpl = dp_opt_get_cstring(kr->krb5_ctx->opts, KRB5_CCNAME_TMPL); } 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)); } else { kr->ccname = talloc_strdup(kr, ccname); } if (!kr->ccname) goto fail; cc_be = sss_krb5_get_type(kr->ccname); switch (cc_be) { case SSS_KRB5_TYPE_FILE: kr->krb5_ctx->cc_be = &file_cc; break; #ifdef HAVE_KRB5_DIRCACHE case SSS_KRB5_TYPE_DIR: kr->krb5_ctx->cc_be = &dir_cc; break; #endif /* HAVE_KRB5_DIRCACHE */ default: if (tmpl[0] != '/') { DEBUG(SSSDBG_OP_FAILURE, ("Unkown ccname database\n")); ret = EINVAL; goto fail; } DEBUG(SSSDBG_CONF_SETTINGS, ("The ccname template was " "missing an explicit type, but looks like an absolute " "path specifier. Assuming FILE:\n")); kr->krb5_ctx->cc_be = &file_cc; break; } DEBUG(SSSDBG_FUNC_DATA, ("ccname [%s] uid [%llu] gid [%llu]\n", kr->ccname, kr->uid, kr->gid)); ret = kr->krb5_ctx->cc_be->create(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_CHILD_TEST_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; } 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) { ctx->kr->krb5_ctx->cc_be->remove(ctx->res->ccname); } free(password); talloc_free(ctx); poptFreeContext(pc); return ret; }