2N/A/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2N/A/*
2N/A * Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved.
2N/A */
2N/A#include "k5-int.h"
2N/A#include "int-proto.h"
2N/A
2N/A/* Return true if configuration demands that a keytab be present. (By default
2N/A * verification will be skipped if no keytab exists.) */
2N/A/*
2N/A * Solaris Kerberos: We differ in that verification will not be skipped if
2N/A * there is no keytab unless verify_ap_req_nofail is explicitly set to false.
2N/A */
2N/Astatic krb5_boolean
2N/Anofail(krb5_context context, krb5_verify_init_creds_opt *options,
2N/A krb5_creds *creds)
2N/A{
2N/A int val;
2N/A
2N/A if (options &&
2N/A (options->flags & KRB5_VERIFY_INIT_CREDS_OPT_AP_REQ_NOFAIL))
2N/A return (options->ap_req_nofail != 0);
2N/A if (krb5int_libdefault_boolean(context, &creds->client->realm,
2N/A KRB5_CONF_VERIFY_AP_REQ_NOFAIL,
2N/A &val) == 0)
2N/A return (val != 0);
2N/A /* Solaris Kerberos: we default to TRUE unlike MIT */
2N/A return TRUE;
2N/A}
2N/A
2N/Astatic krb5_error_code
2N/Acopy_creds_except(krb5_context context, krb5_ccache incc,
2N/A krb5_ccache outcc, krb5_principal princ)
2N/A{
2N/A krb5_error_code code;
2N/A krb5_flags flags;
2N/A krb5_cc_cursor cur;
2N/A krb5_creds creds;
2N/A
2N/A flags = 0; /* turns off OPENCLOSE mode */
2N/A if ((code = krb5_cc_set_flags(context, incc, flags)))
2N/A return(code);
2N/A if ((code = krb5_cc_set_flags(context, outcc, flags)))
2N/A return(code);
2N/A
2N/A if ((code = krb5_cc_start_seq_get(context, incc, &cur)))
2N/A goto cleanup;
2N/A
2N/A while (!(code = krb5_cc_next_cred(context, incc, &cur, &creds))) {
2N/A if (krb5_principal_compare(context, princ, creds.server))
2N/A continue;
2N/A
2N/A code = krb5_cc_store_cred(context, outcc, &creds);
2N/A krb5_free_cred_contents(context, &creds);
2N/A if (code)
2N/A goto cleanup;
2N/A }
2N/A
2N/A if (code != KRB5_CC_END)
2N/A goto cleanup;
2N/A
2N/A code = 0;
2N/A
2N/Acleanup:
2N/A flags = KRB5_TC_OPENCLOSE;
2N/A
2N/A if (code)
2N/A krb5_cc_set_flags(context, incc, flags);
2N/A else
2N/A code = krb5_cc_set_flags(context, incc, flags);
2N/A
2N/A if (code)
2N/A krb5_cc_set_flags(context, outcc, flags);
2N/A else
2N/A code = krb5_cc_set_flags(context, outcc, flags);
2N/A
2N/A return(code);
2N/A}
2N/A
2N/Astatic krb5_error_code
2N/Aget_vfy_cred(krb5_context context, krb5_creds *creds, krb5_principal server,
2N/A krb5_keytab keytab, krb5_ccache *ccache_arg)
2N/A{
2N/A krb5_error_code ret;
2N/A krb5_ccache ccache;
2N/A krb5_creds in_creds, *out_creds;
2N/A krb5_auth_context authcon;
2N/A krb5_data ap_req;
2N/A
2N/A ccache = NULL;
2N/A out_creds = NULL;
2N/A authcon = NULL;
2N/A ap_req.data = NULL;
2N/A /* If the creds are for the server principal, we're set, just do a mk_req.
2N/A * Otherwise, do a get_credentials first.
2N/A */
2N/A
2N/A if (krb5_principal_compare(context, server, creds->server)) {
2N/A /* make an ap_req */
2N/A if ((ret = krb5_mk_req_extended(context, &authcon, 0, NULL, creds,
2N/A &ap_req)))
2N/A goto cleanup;
2N/A } else {
2N/A /*
2N/A * Solaris Kerberos: being careful not to allow an attack where the
2N/A * default realm is coming from DNS. If this is the case then the
2N/A * client princ realm must be the same as the server princ's realm used
2N/A * to verify the client's TGT. Note that this attack can be thwarted
2N/A * when the default realm is explictly configured.
2N/A */
2N/A char *temp_realm = NULL;
2N/A
2N/A ret = profile_get_string(context->profile, KRB5_CONF_LIBDEFAULTS,
2N/A KRB5_CONF_DEFAULT_REALM, NULL, NULL,
2N/A &temp_realm);
2N/A if (ret)
2N/A goto cleanup;
2N/A
2N/A if (temp_realm == NULL) {
2N/A /*
2N/A * If here then the default realm is not explicitly configured on
2N/A * the system. Need to verify the client's realm is the same as
2N/A * the server's.
2N/A */
2N/A if (!krb5_realm_compare(context, creds->client, server)) {
2N/A /* Indicate a default realm needs to be configured */
2N/A ret = KRB5_CONFIG_NODEFREALM;
2N/A goto cleanup;
2N/A }
2N/A } else {
2N/A profile_release_string(temp_realm);
2N/A }
2N/A /* this is unclean, but it's the easiest way without ripping the
2N/A library into very small pieces. store the client's initial cred
2N/A in a memory ccache, then call the library. Later, we'll copy
2N/A everything except the initial cred into the ccache we return to
2N/A the user. A clean implementation would involve library
2N/A internals with a coherent idea of "in" and "out". */
2N/A
2N/A /* insert the initial cred into the ccache */
2N/A
2N/A if ((ret = krb5_cc_new_unique(context, "MEMORY", NULL, &ccache))) {
2N/A ccache = NULL;
2N/A goto cleanup;
2N/A }
2N/A
2N/A if ((ret = krb5_cc_initialize(context, ccache, creds->client)))
2N/A goto cleanup;
2N/A
2N/A if ((ret = krb5_cc_store_cred(context, ccache, creds)))
2N/A goto cleanup;
2N/A
2N/A /* set up for get_creds */
2N/A memset(&in_creds, 0, sizeof(in_creds));
2N/A in_creds.client = creds->client;
2N/A in_creds.server = server;
2N/A if ((ret = krb5_timeofday(context, &in_creds.times.endtime)))
2N/A goto cleanup;
2N/A in_creds.times.endtime += 5*60;
2N/A
2N/A if ((ret = krb5_get_credentials(context, 0, ccache, &in_creds,
2N/A &out_creds)))
2N/A goto cleanup;
2N/A
2N/A /* make an ap_req */
2N/A if ((ret = krb5_mk_req_extended(context, &authcon, 0, NULL, out_creds,
2N/A &ap_req)))
2N/A goto cleanup;
2N/A }
2N/A
2N/A /* wipe the auth context for mk_req */
2N/A if (authcon) {
2N/A krb5_auth_con_free(context, authcon);
2N/A authcon = NULL;
2N/A }
2N/A
2N/A /* verify the ap_req */
2N/A
2N/A if ((ret = krb5_rd_req(context, &authcon, &ap_req, server, keytab,
2N/A NULL, NULL)))
2N/A goto cleanup;
2N/A
2N/A /* if we get this far, then the verification succeeded. We can
2N/A still fail if the library stuff here fails, but that's it */
2N/A
2N/A if (ccache_arg && ccache) {
2N/A if (*ccache_arg == NULL) {
2N/A krb5_ccache retcc;
2N/A
2N/A retcc = NULL;
2N/A
2N/A if ((ret = krb5_cc_resolve(context, "MEMORY:rd_req2", &retcc)) ||
2N/A (ret = krb5_cc_initialize(context, retcc, creds->client)) ||
2N/A (ret = copy_creds_except(context, ccache, retcc,
2N/A creds->server))) {
2N/A if (retcc)
2N/A krb5_cc_destroy(context, retcc);
2N/A } else {
2N/A *ccache_arg = retcc;
2N/A }
2N/A } else {
2N/A ret = copy_creds_except(context, ccache, *ccache_arg,
2N/A server);
2N/A }
2N/A }
2N/A
2N/A /* if any of the above paths returned an errors, then ret is set accordingly.
2N/A * Either that, or it's zero, which is fine, too
2N/A */
2N/A
2N/Acleanup:
2N/A if (ccache)
2N/A krb5_cc_destroy(context, ccache);
2N/A if (out_creds)
2N/A krb5_free_creds(context, out_creds);
2N/A if (authcon)
2N/A krb5_auth_con_free(context, authcon);
2N/A if (ap_req.data)
2N/A free(ap_req.data);
2N/A
2N/A return(ret);
2N/A}
2N/A
2N/A/* Free the principals in plist and plist itself. */
2N/Astatic void
2N/Afree_princ_list(krb5_context context, krb5_principal *plist)
2N/A{
2N/A size_t i;
2N/A
2N/A if (plist == NULL)
2N/A return;
2N/A for (i = 0; plist[i] != NULL; i++)
2N/A krb5_free_principal(context, plist[i]);
2N/A free(plist);
2N/A}
2N/A
2N/A/* Add princ to plist if it isn't already there. */
2N/Astatic krb5_error_code
2N/Aadd_princ_list(krb5_context context, krb5_const_principal princ,
2N/A krb5_principal **plist)
2N/A{
2N/A size_t i;
2N/A krb5_principal *newlist;
2N/A
2N/A /* Check if princ is already in plist, and count the elements. */
2N/A for (i = 0; (*plist) != NULL && (*plist)[i] != NULL; i++) {
2N/A if (krb5_principal_compare(context, princ, (*plist)[i]))
2N/A return 0;
2N/A }
2N/A
2N/A newlist = realloc(*plist, (i + 2) * sizeof(*newlist));
2N/A if (newlist == NULL)
2N/A return ENOMEM;
2N/A *plist = newlist;
2N/A newlist[i] = newlist[i + 1] = NULL; /* terminate the list */
2N/A return krb5_copy_principal(context, princ, &newlist[i]);
2N/A}
2N/A
2N/A/* Return a list of all unique host service princs in keytab. */
2N/Astatic krb5_error_code
2N/Aget_host_princs_from_keytab(krb5_context context, krb5_keytab keytab,
2N/A krb5_principal **princ_list_out)
2N/A{
2N/A krb5_error_code ret;
2N/A krb5_kt_cursor cursor;
2N/A krb5_keytab_entry kte;
2N/A krb5_principal *plist = NULL, p;
2N/A
2N/A *princ_list_out = NULL;
2N/A
2N/A ret = krb5_kt_start_seq_get(context, keytab, &cursor);
2N/A if (ret)
2N/A goto cleanup;
2N/A
2N/A while ((ret = krb5_kt_next_entry(context, keytab, &kte, &cursor)) == 0) {
2N/A p = kte.principal;
2N/A if (p->length == 2 && data_eq_string(p->data[0], "host"))
2N/A ret = add_princ_list(context, p, &plist);
2N/A krb5_kt_free_entry(context, &kte);
2N/A if (ret)
2N/A break;
2N/A }
2N/A (void)krb5_kt_end_seq_get(context, keytab, &cursor);
2N/A if (ret == KRB5_KT_END)
2N/A ret = 0;
2N/A if (ret)
2N/A goto cleanup;
2N/A
2N/A *princ_list_out = plist;
2N/A plist = NULL;
2N/A
2N/Acleanup:
2N/A free_princ_list(context, plist);
2N/A return ret;
2N/A}
2N/A
2N/Akrb5_error_code KRB5_CALLCONV
2N/Akrb5_verify_init_creds(krb5_context context, krb5_creds *creds,
2N/A krb5_principal server, krb5_keytab keytab,
2N/A krb5_ccache *ccache,
2N/A krb5_verify_init_creds_opt *options)
2N/A{
2N/A krb5_error_code ret;
2N/A krb5_principal *host_princs = NULL;
2N/A krb5_keytab defkeytab = NULL;
2N/A krb5_keytab_entry kte;
2N/A krb5_boolean have_keys = FALSE;
2N/A size_t i;
2N/A
2N/A if (keytab == NULL) {
2N/A ret = krb5_kt_default(context, &defkeytab);
2N/A if (ret)
2N/A goto cleanup;
2N/A keytab = defkeytab;
2N/A }
2N/A
2N/A if (server != NULL) {
2N/A /* Check if server exists in keytab first. */
2N/A ret = krb5_kt_get_entry(context, keytab, server, 0, 0, &kte);
2N/A if (ret)
2N/A goto cleanup;
2N/A krb5_kt_free_entry(context, &kte);
2N/A have_keys = TRUE;
2N/A ret = get_vfy_cred(context, creds, server, keytab, ccache);
2N/A } else {
2N/A /* Try using the host service principals from the keytab. */
2N/A if (keytab->ops->start_seq_get == NULL) {
2N/A ret = EINVAL;
2N/A goto cleanup;
2N/A }
2N/A ret = get_host_princs_from_keytab(context, keytab, &host_princs);
2N/A if (ret)
2N/A goto cleanup;
2N/A if (host_princs == NULL) {
2N/A ret = KRB5_KT_NOTFOUND;
2N/A goto cleanup;
2N/A }
2N/A have_keys = TRUE;
2N/A
2N/A /* Try all host principals until one succeeds or they all fail. */
2N/A for (i = 0; host_princs[i] != NULL; i++) {
2N/A ret = get_vfy_cred(context, creds, host_princs[i], keytab, ccache);
2N/A if (ret == 0)
2N/A break;
2N/A }
2N/A }
2N/A
2N/Acleanup:
2N/A /* If we have no key to verify with, pretend to succeed unless
2N/A * configuration directs otherwise. */
2N/A if (!have_keys && !nofail(context, options, creds))
2N/A ret = 0;
2N/A
2N/A if (defkeytab != NULL)
2N/A krb5_kt_close(context, defkeytab);
2N/A krb5_free_principal(context, server);
2N/A free_princ_list(context, host_princs);
2N/A
2N/A return ret;
2N/A}