means that short hostnames will not be canonicalized to
fully-qualified hostnames. The default value is true.
+ If this option is set to ``fallback`` (new in release 1.18), DNS
+ canonicalization will only be performed the server hostname is not
+ found with the original name when requesting credentials.
+
**dns_lookup_kdc**
Indicate whether DNS SRV records should be used to locate the KDCs
and other servers for a realm, if they are not listed in the
void
k5_plugin_free_context(krb5_context context);
+enum dns_canonhost {
+ CANONHOST_FALSE = 0,
+ CANONHOST_TRUE = 1,
+ CANONHOST_FALLBACK = 2
+};
+
struct _kdb5_dal_handle; /* private, in kdb5.h */
typedef struct _kdb5_dal_handle kdb5_dal_handle;
struct _kdb_log_context;
krb5_boolean allow_weak_crypto;
krb5_boolean ignore_acceptor_hostname;
- krb5_boolean dns_canonicalize_hostname;
+ enum dns_canonhost dns_canonicalize_hostname;
krb5_trace_callback trace_callback;
void *trace_callback_data;
#define TRACE_FAST_REQUIRED(c) \
TRACE(c, "Using FAST due to KRB5_FAST_REQUIRED flag")
+#define TRACE_GET_CREDS_FALLBACK(c, hostname) \
+ TRACE(c, "Falling back to canonicalized server hostname {str}", hostname)
+
#define TRACE_GIC_PWD_CHANGED(c) \
TRACE(c, "Getting initial TGT with changed password")
#define TRACE_GIC_PWD_CHANGEPW(c, tries) \
#include "k5-int.h"
#include "int-proto.h"
+#include "os-proto.h"
#include "fast.h"
/*
return EINVAL;
}
+static krb5_error_code
+try_get_creds(krb5_context context, krb5_flags options, krb5_ccache ccache,
+ krb5_creds *in_creds, krb5_creds *creds_out)
+{
+ krb5_error_code code;
+ krb5_tkt_creds_context ctx = NULL;
+
+ code = krb5_tkt_creds_init(context, ccache, in_creds, options, &ctx);
+ if (code)
+ goto cleanup;
+ code = krb5_tkt_creds_get(context, ctx);
+ if (code)
+ goto cleanup;
+ code = krb5_tkt_creds_get_creds(context, ctx, creds_out);
+
+cleanup:
+ krb5_tkt_creds_free(context, ctx);
+ return code;
+}
+
krb5_error_code KRB5_CALLCONV
krb5_get_credentials(krb5_context context, krb5_flags options,
krb5_ccache ccache, krb5_creds *in_creds,
{
krb5_error_code code;
krb5_creds *ncreds = NULL;
- krb5_tkt_creds_context ctx = NULL;
+ krb5_creds canon_creds, store_creds;
+ krb5_principal_data canon_server;
+ krb5_data canon_components[2];
+ char *hostname = NULL, *canon_hostname = NULL;
*out_creds = NULL;
if (ncreds == NULL)
goto cleanup;
- /* Make and execute a krb5_tkt_creds context to get the credential. */
- code = krb5_tkt_creds_init(context, ccache, in_creds, options, &ctx);
- if (code != 0)
+ code = try_get_creds(context, options, ccache, in_creds, ncreds);
+ if (!code) {
+ *out_creds = ncreds;
+ return 0;
+ }
+
+ /* Possibly try again with the canonicalized hostname, if the server is
+ * host-based and we are configured for fallback canonicalization. */
+ if (code != KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN)
goto cleanup;
- code = krb5_tkt_creds_get(context, ctx);
- if (code != 0)
+ if (context->dns_canonicalize_hostname != CANONHOST_FALLBACK)
goto cleanup;
- code = krb5_tkt_creds_get_creds(context, ctx, ncreds);
- if (code != 0)
+ if (in_creds->server->type != KRB5_NT_SRV_HST ||
+ in_creds->server->length != 2)
goto cleanup;
+ hostname = k5memdup0(in_creds->server->data[1].data,
+ in_creds->server->data[1].length, &code);
+ if (hostname == NULL)
+ goto cleanup;
+ code = k5_expand_hostname(context, hostname, TRUE, &canon_hostname);
+ if (code)
+ goto cleanup;
+
+ TRACE_GET_CREDS_FALLBACK(context, canon_hostname);
+
+ /* Make shallow copies of in_creds and its server to alter the hostname. */
+ canon_components[0] = in_creds->server->data[0];
+ canon_components[1] = string2data(canon_hostname);
+ canon_server = *in_creds->server;
+ canon_server.data = canon_components;
+ canon_creds = *in_creds;
+ canon_creds.server = &canon_server;
+
+ code = try_get_creds(context, options | KRB5_GC_NO_STORE, ccache,
+ &canon_creds, ncreds);
+ if (code)
+ goto cleanup;
+
+ if (!(options & KRB5_GC_NO_STORE)) {
+ /* Store the creds under the originally requested server name. The
+ * ccache layer will also store them under the ticket server name. */
+ store_creds = *ncreds;
+ store_creds.server = in_creds->server;
+ (void)krb5_cc_store_cred(context, ccache, &store_creds);
+ }
+
*out_creds = ncreds;
ncreds = NULL;
cleanup:
+ free(hostname);
+ free(canon_hostname);
krb5_free_creds(context, ncreds);
- krb5_tkt_creds_free(context, ctx);
return code;
}
return retval;
}
+static krb5_error_code
+get_tristate(krb5_context ctx, const char *name, const char *third_option,
+ int third_option_val, int def_val, int *val_out)
+{
+ krb5_error_code retval;
+ char *str;
+ int match;
+
+ retval = profile_get_boolean(ctx->profile, KRB5_CONF_LIBDEFAULTS, name,
+ NULL, def_val, val_out);
+ if (retval != PROF_BAD_BOOLEAN)
+ return retval;
+ retval = profile_get_string(ctx->profile, KRB5_CONF_LIBDEFAULTS, name,
+ NULL, NULL, &str);
+ if (retval)
+ return retval;
+ match = (strcasecmp(third_option, str) == 0);
+ free(str);
+ if (!match)
+ return EINVAL;
+ *val_out = third_option_val;
+ return 0;
+}
+
krb5_error_code KRB5_CALLCONV
krb5_init_context(krb5_context *context)
{
goto cleanup;
ctx->ignore_acceptor_hostname = tmp;
- retval = get_boolean(ctx, KRB5_CONF_DNS_CANONICALIZE_HOSTNAME, 1, &tmp);
+ retval = get_tristate(ctx, KRB5_CONF_DNS_CANONICALIZE_HOSTNAME, "fallback",
+ CANONHOST_FALLBACK, 1, &tmp);
if (retval)
goto cleanup;
ctx->dns_canonicalize_hostname = tmp;
ctx->udp_pref_limit = 2345;
ctx->use_conf_ktypes = TRUE;
ctx->ignore_acceptor_hostname = TRUE;
- ctx->dns_canonicalize_hostname = FALSE;
+ ctx->dns_canonicalize_hostname = CANONHOST_FALSE;
free(ctx->plugin_base_dir);
check((ctx->plugin_base_dir = strdup("/a/b/c/d")) != NULL);
void *data;
};
+krb5_error_code k5_expand_hostname(krb5_context context, const char *host,
+ krb5_boolean is_fallback,
+ char **canonhost_out);
+
krb5_error_code k5_locate_server(krb5_context, const krb5_data *realm,
struct serverlist *serverlist,
enum locate_service_type svc,
return value;
}
-krb5_error_code KRB5_CALLCONV
-krb5_expand_hostname(krb5_context context, const char *host,
- char **canonhost_out)
+krb5_error_code
+k5_expand_hostname(krb5_context context, const char *host,
+ krb5_boolean is_fallback, char **canonhost_out)
{
struct addrinfo *ai = NULL, hint;
char namebuf[NI_MAXHOST], *copy, *p;
int err;
const char *canonhost;
+ krb5_boolean use_dns;
*canonhost_out = NULL;
canonhost = host;
- if (context->dns_canonicalize_hostname) {
+ use_dns = (context->dns_canonicalize_hostname == CANONHOST_TRUE ||
+ (is_fallback &&
+ context->dns_canonicalize_hostname == CANONHOST_FALLBACK));
+ if (use_dns) {
/* Try a forward lookup of the hostname. */
memset(&hint, 0, sizeof(hint));
hint.ai_flags = AI_CANONNAME;
return (*canonhost_out == NULL) ? ENOMEM : 0;
}
+krb5_error_code KRB5_CALLCONV
+krb5_expand_hostname(krb5_context context, const char *host,
+ char **canonhost_out)
+{
+ return k5_expand_hostname(context, host, FALSE, canonhost_out);
+}
+
/* If hostname appears to have a :port or :instance trailer (used in MSSQLSvc
* principals), return a pointer to the separator. Otherwise return NULL. */
static const char *
krb5_principal client, server;
krb5_ccache ccache;
krb5_creds in_creds, *creds;
+ krb5_ticket *ticket;
krb5_flags options = 0;
char *name;
int c;
in_creds.client = client;
in_creds.server = server;
check(krb5_get_credentials(ctx, options, ccache, &in_creds, &creds));
- check(krb5_unparse_name(ctx, creds->server, &name));
+ check(krb5_decode_ticket(&creds->ticket, &ticket));
+ check(krb5_unparse_name(ctx, ticket->server, &name));
printf("%s\n", name);
+ krb5_free_ticket(ctx, ticket);
krb5_free_unparsed_name(ctx, name);
krb5_free_creds(ctx, creds);
krb5_free_principal(ctx, client);
'mit.edu': 'R3'}}
no_rdns_conf = {'libdefaults': {'rdns': 'false'}}
no_canon_conf = {'libdefaults': {'dns_canonicalize_hostname': 'false'}}
+fallback_canon_conf = {'libdefaults':
+ {'rdns': 'false',
+ 'dns_canonicalize_hostname': 'fallback'}}
-realm = K5Realm(create_kdb=False, krb5_conf=conf)
+realm = K5Realm(realm='R1', create_host=False, krb5_conf=conf)
no_rdns = realm.special_env('no_rdns', False, krb5_conf=no_rdns_conf)
no_canon = realm.special_env('no_canon', False, krb5_conf=no_canon_conf)
+fallback_canon = realm.special_env('fallback_canon', False,
+ krb5_conf=fallback_canon_conf)
def testbase(host, nametype, princhost, princrealm, env=None):
# Run the sn2princ harness with a specified host and name type and
# Test with the unknown name type.
testbase(host, 'unknown', princhost, princrealm)
+def testfc(host, princhost, princrealm):
+ # Test with the host-based name type with canonicalization fallback.
+ testbase(host, 'srv-hst', princhost, princrealm, env=fallback_canon)
+
# With the unknown principal type, we do not canonicalize or downcase,
# but we do remove a trailing period and look up the realm.
mark('unknown type')
oname = 'ptr-mismatch.kerberos.org'
fname = 'www.kerberos.org'
+# Test fallback canonicalization krb5_sname_to_principal() results
+# (same as dns_canonicalize_hostname=false).
+mark('dns_canonicalize_host=fallback')
+testfc(oname, oname, 'R1')
+
+# Test fallback canonicalization in krb5_get_credentials().
+oprinc = 'host/' + oname
+fprinc = 'host/' + fname
+shutil.copy(realm.ccache, realm.ccache + '.save')
+realm.addprinc(fprinc)
+# oprinc doesn't exist, so we get the canonicalized fprinc as a fallback.
+msgs = ('Falling back to canonicalized server hostname ' + fname,)
+realm.run(['./gcred', 'srv-hst', oprinc], env=fallback_canon,
+ expected_msg=fprinc, expected_trace=msgs)
+realm.addprinc(oprinc)
+# oprinc now exists, but we still get the fprinc ticket from the cache.
+realm.run(['./gcred', 'srv-hst', oprinc], env=fallback_canon,
+ expected_msg=fprinc)
+# Without the cached result, we sould get oprinc in preference to fprinc.
+os.rename(realm.ccache + '.save', realm.ccache)
+realm.run(['./gcred', 'srv-hst', oprinc], env=fallback_canon,
+ expected_msg=oprinc)
+
# Verify forward resolution before testing for it.
try:
ai = socket.getaddrinfo(oname, None, 0, 0, 0, socket.AI_CANONNAME)