library will tolerate before assuming that a Kerberos message is
invalid. The default value is @value{DefaultClockskew}.
+@itemx ignore_acceptor_hostname
+When accepting GSSAPI or krb5 security contexts for host-based service
+principals, ignore any hostname passed by the calling application and
+allow any service principal present in the keytab which matches the
+service name and realm name (if given). This option can improve
+the administrative flexibility of server applications multi-homed hosts,
+but can compromise the security of virtual hosting environments. The
+default value is false.
+
@itemx k5login_authoritative
If the value of this relation is true (the default), principals must
be listed in a local user's k5login file to be granted login access,
that the library will tolerate before assuming that a Kerberos message
is invalid. The default value is 300 seconds, or five minutes.
+.IP ignore_acceptor_hostname
+When accepting GSSAPI or krb5 security contexts for host-based service
+principals, ignore any hostname passed by the calling application and
+allow any service principal present in the keytab which matches the
+service name and realm name (if given). This option can improve the
+administrative flexibility of server applications on multi-homed
+hosts, but can compromise the security of virtual hosting
+environments. The default value is false.
+
.IP k5login_authoritative
If the value of this relation is true (the default), principals must
be listed in a local user's k5login file to be granted login access,
#define KRB5_CONF_EXTRA_ADDRESSES "extra_addresses"
#define KRB5_CONF_FORWARDABLE "forwardable"
#define KRB5_CONF_HOST_BASED_SERVICES "host_based_services"
+#define KRB5_CONF_IGNORE_ACCEPTOR_HOSTNAME "ignore_acceptor_hostname"
#define KRB5_CONF_IPROP_ENABLE "iprop_enable"
#define KRB5_CONF_IPROP_MASTER_ULOGSIZE "iprop_master_ulogsize"
#define KRB5_CONF_IPROP_PORT "iprop_port"
struct _kdb_log_context *kdblog_context;
krb5_boolean allow_weak_crypto;
+ krb5_boolean ignore_acceptor_hostname;
krb5_trace_callback trace_callback;
void *trace_callback_data;
krb5_sname_to_principal(krb5_context context, const char *hostname, const char *sname,
krb5_int32 type, krb5_principal *ret_princ);
+/** Return true if @a princ matches @a matching, false otherwise.
+ *
+ * A matching principal is a host-based principal with an empty realm and/or
+ * second data component (hostname). Profile configuration may cause the
+ * hostname to be ignored even if it is present. A principal matches a
+ * matching principal if the principal has the same non-empty (and non-ignored)
+ * components of the matching principal.
+ *
+ * If @a matching is NULL, return true. If @a matching is not a matching
+ * principal, return the value of krb5_principal_compare(context, matching,
+ * princ).
+ */
+krb5_boolean KRB5_CALLCONV
+krb5_sname_match(krb5_context context, krb5_const_principal matching,
+ krb5_const_principal princ);
+
/**
* @brief Change the password for an existing Kerberos account.
*
/* copy the client principle into it... */
if ((retval =
- kg_init_name(context, creds[0]->client, NULL, 0, &cred->name))) {
+ kg_init_name(context, creds[0]->client, NULL, NULL, NULL, 0,
+ &cred->name))) {
k5_mutex_destroy(&cred->lock);
retval = ENOMEM; /* out of memory? */
xfree(cred); /* clean up memory on failure */
krb5_flags ap_req_options = 0;
krb5_enctype negotiated_etype;
krb5_authdata_context ad_context = NULL;
+ krb5_principal accprinc = NULL;
code = krb5int_accessor (&kaccess, KRB5INT_ACCESS_VERSION);
if (code) {
}
}
- if ((code = krb5_rd_req(context, &auth_context, &ap_req,
- cred->default_identity ? NULL : cred->name->princ,
- cred->keytab,
- &ap_req_options,
- &ticket))) {
+ if (!cred->default_identity) {
+ if ((code = kg_acceptor_princ(context, cred->name, &accprinc))) {
+ major_status = GSS_S_FAILURE;
+ goto fail;
+ }
+ }
+
+ if ((code = krb5_rd_req(context, &auth_context, &ap_req, accprinc,
+ cred->keytab, &ap_req_options, &ticket))) {
major_status = GSS_S_FAILURE;
goto fail;
}
major_status = GSS_S_FAILURE;
goto fail;
}
- if ((code = kg_init_name(context, ticket->server, NULL, 0, &ctx->here))) {
+ if ((code = kg_init_name(context, ticket->server, NULL, NULL, NULL, 0,
+ &ctx->here))) {
major_status = GSS_S_FAILURE;
goto fail;
}
major_status = GSS_S_FAILURE;
goto fail;
}
- if ((code = kg_init_name(context, authdat->client,
+ if ((code = kg_init_name(context, authdat->client, NULL, NULL,
ad_context, KG_INIT_NAME_NO_COPY, &ctx->there))) {
major_status = GSS_S_FAILURE;
goto fail;
krb_error_data.error = code;
(void) krb5_us_timeofday(context, &krb_error_data.stime,
&krb_error_data.susec);
- krb_error_data.server = cred->name ? cred->name->princ : NULL;
code = krb5_mk_error(context, &krb_error_data, &scratch);
if (code)
return GSS_S_COMPLETE;
}
+/* Try to verify that keytab contains at least one entry for name. Return 0 if
+ * it does, KRB5_KT_NOTFOUND if it doesn't, or another error as appropriate. */
+static krb5_error_code
+check_keytab(krb5_context context, krb5_keytab kt, krb5_gss_name_t name)
+{
+ krb5_error_code code;
+ krb5_keytab_entry ent;
+ krb5_kt_cursor cursor;
+ krb5_principal accprinc = NULL;
+ krb5_boolean match;
+ char *princname;
+
+ if (name->service == NULL) {
+ code = krb5_kt_get_entry(context, kt, name->princ, 0, 0, &ent);
+ if (code == 0)
+ krb5_kt_free_entry(context, &ent);
+ return code;
+ }
+
+ /* If we can't iterate through the keytab, skip this check. */
+ if (kt->ops->start_seq_get == NULL)
+ return 0;
+
+ /* Get the partial principal for the acceptor name. */
+ code = kg_acceptor_princ(context, name, &accprinc);
+ if (code)
+ return code;
+
+ /* Scan the keytab for host-based entries matching accprinc. */
+ code = krb5_kt_start_seq_get(context, kt, &cursor);
+ if (code)
+ goto cleanup;
+ while ((code = krb5_kt_next_entry(context, kt, &ent, &cursor)) == 0) {
+ match = krb5_sname_match(context, accprinc, ent.principal);
+ (void)krb5_free_keytab_entry_contents(context, &ent);
+ if (match)
+ break;
+ }
+ (void)krb5_kt_end_seq_get(context, kt, &cursor);
+ if (code == KRB5_KT_END) {
+ code = KRB5_KT_NOTFOUND;
+ if (krb5_unparse_name(context, accprinc, &princname) == 0) {
+ krb5_set_error_message(context, code, "No key table entry "
+ "found matching %s", princname);
+ free(princname);
+ }
+ }
+
+cleanup:
+ krb5_free_principal(context, accprinc);
+ return code;
+}
+
/* get credentials corresponding to a key in the krb5 keytab.
If successful, set the keytab-specific fields in cred
*/
static OM_uint32
acquire_accept_cred(krb5_context context,
OM_uint32 *minor_status,
- krb5_principal desired_princ,
+ krb5_gss_name_t desired_name,
krb5_keytab req_keytab,
krb5_gss_cred_id_rec *cred)
{
krb5_error_code code;
krb5_keytab kt;
- krb5_keytab_entry entry;
assert(cred->keytab == NULL);
return GSS_S_CRED_UNAVAIL;
}
- if (desired_princ != NULL) {
- code = krb5_kt_get_entry(context, kt, desired_princ, 0, 0, &entry);
+ if (desired_name != NULL) {
+ code = check_keytab(context, kt, desired_name);
if (code) {
krb5_kt_close(context, kt);
if (code == KRB5_KT_NOTFOUND) {
*minor_status = code;
return GSS_S_CRED_UNAVAIL;
}
- krb5_kt_free_entry(context, &entry);
assert(cred->name == NULL);
- code = kg_init_name(context, desired_princ, NULL, 0, &cred->name);
+ code = kg_duplicate_name(context, desired_name, 0, &cred->name);
if (code) {
*minor_status = code;
return GSS_S_FAILURE;
}
/* Open the replay cache for this principal. */
- code = krb5_get_server_rcache(context,
- krb5_princ_component(context, desired_princ, 0),
+ code = krb5_get_server_rcache(context, &desired_name->princ->data[0],
&cred->rcache);
if (code) {
*minor_status = code;
* cred->name to the credentials cache principal name.
*/
if (cred->name == NULL) {
- if ((code = kg_init_name(context, ccache_princ, NULL,
+ if ((code = kg_init_name(context, ccache_princ, NULL, NULL, NULL,
KG_INIT_NAME_NO_COPY, &cred->name))) {
krb5_free_principal(context, ccache_princ);
krb5_cc_close(context, ccache);
{
krb5_context context = NULL;
krb5_gss_cred_id_t cred = NULL;
+ krb5_gss_name_t name = (krb5_gss_name_t)args->desired_name;
OM_uint32 ret;
krb5_error_code code = 0;
- krb5_principal desired_princ = NULL;
/* make sure all outputs are valid */
*output_cred_handle = GSS_C_NO_CREDENTIAL;
cred->usage = args->cred_usage;
cred->name = NULL;
cred->iakerb_mech = args->iakerb;
- cred->default_identity = (args->desired_name == GSS_C_NO_NAME);
+ cred->default_identity = (name == NULL);
#ifndef LEAN_CLIENT
cred->keytab = NULL;
#endif /* LEAN_CLIENT */
goto error_out;
}
- if (args->desired_name != GSS_C_NO_NAME)
- desired_princ = ((krb5_gss_name_t)args->desired_name)->princ;
-
#ifndef LEAN_CLIENT
/*
* If requested, acquire credentials for accepting. This will fill
* in cred->name if desired_princ is specified.
*/
if (args->cred_usage == GSS_C_ACCEPT || args->cred_usage == GSS_C_BOTH) {
- ret = acquire_accept_cred(context, minor_status,
- desired_princ,
- args->keytab, cred);
+ ret = acquire_accept_cred(context, minor_status, name, args->keytab,
+ cred);
if (ret != GSS_S_COMPLETE)
goto error_out;
}
*/
if (args->cred_usage == GSS_C_INITIATE || args->cred_usage == GSS_C_BOTH) {
ret = acquire_init_cred(context, minor_status, args->ccache,
- desired_princ, args->password, cred);
+ name ? name->princ : NULL, args->password,
+ cred);
if (ret != GSS_S_COMPLETE)
goto error_out;
}
/** internal types **/
typedef struct _krb5_gss_name_rec {
- krb5_principal princ; /* immutable */
- k5_mutex_t lock; /* protects ad_context only for now */
+ krb5_principal princ; /* immutable */
+ char *service; /* immutable */
+ char *host; /* immutable */
+ k5_mutex_t lock; /* protects ad_context only for now */
krb5_authdata_context ad_context;
} krb5_gss_name_rec, *krb5_gss_name_t;
#define KG_INIT_NAME_NO_COPY 0x2
krb5_error_code
-kg_init_name(krb5_context context,
- krb5_principal principal,
- krb5_authdata_context ad_context,
- krb5_flags flags,
- krb5_gss_name_t *name);
+kg_init_name(krb5_context context, krb5_principal principal,
+ char *service, char *host, krb5_authdata_context ad_context,
+ krb5_flags flags, krb5_gss_name_t *name);
krb5_error_code
kg_release_name(krb5_context context,
krb5_gss_name_t name1,
krb5_gss_name_t name2);
+krb5_boolean
+kg_acceptor_princ(krb5_context context, krb5_gss_name_t name,
+ krb5_principal *princ_out);
+
OM_uint32
krb5_gss_display_name_ext(OM_uint32 *minor_status,
gss_name_t name,
return 0;
}
+/* Split a host-based name "service[@host]" into allocated strings
+ * placed in *service_out and *host_out (possibly NULL). */
+static krb5_error_code
+parse_hostbased(const char *str, size_t len,
+ char **service_out, char **host_out)
+{
+ const char *at;
+ size_t servicelen, hostlen;
+ char *service, *host = NULL;
+
+ *service_out = *host_out = NULL;
+
+ /* Find the bound of the service name and copy it. */
+ at = memchr(str, '@', len);
+ servicelen = (at == NULL) ? len : (size_t)(at - str);
+ service = xmalloc(servicelen + 1);
+ if (service == NULL)
+ return ENOMEM;
+ memcpy(service, str, servicelen);
+ service[servicelen] = '\0';
+
+ /* If present, copy the hostname. */
+ if (at != NULL) {
+ hostlen = len - servicelen - 1;
+ host = malloc(hostlen + 1);
+ if (host == NULL) {
+ free(service);
+ return ENOMEM;
+ }
+ memcpy(host, at + 1, hostlen);
+ host[hostlen] = '\0';
+ }
+
+ *service_out = service;
+ *host_out = host;
+ return 0;
+}
+
OM_uint32
krb5_gss_import_name(minor_status, input_name_buffer,
input_name_type, output_name)
krb5_principal princ = NULL;
krb5_error_code code;
unsigned char *cp, *end;
- char *tmp = NULL, *tmp2 = NULL, *stringrep;
+ char *tmp = NULL, *tmp2 = NULL, *service = NULL, *host = NULL, *stringrep;
ssize_t length;
#ifndef NO_PASSWORD
struct passwd *pw;
if ((input_name_type != GSS_C_NULL_OID) &&
(g_OID_equal(input_name_type, gss_nt_service_name) ||
g_OID_equal(input_name_type, gss_nt_service_name_v2))) {
- char *service, *host;
-
- tmp = k5alloc(input_name_buffer->length + 1, &code);
- if (tmp == NULL)
+ /* Split the name into service and host (or NULL). */
+ code = parse_hostbased(input_name_buffer->value,
+ input_name_buffer->length, &service, &host);
+ if (code)
goto cleanup;
- memcpy(tmp, input_name_buffer->value, input_name_buffer->length);
- tmp[input_name_buffer->length] = '\0';
-
- service = tmp;
- if ((host = strchr(tmp, '@'))) {
- *host = '\0';
- host++;
- }
-
+ /*
+ * Compute the initiator target name. In some cases this is a waste of
+ * getaddrinfo/getnameinfo queries, but computing the name when we need
+ * it would require a lot of code changes.
+ */
code = krb5_sname_to_principal(context, host, service, KRB5_NT_SRV_HST,
&princ);
if (code)
}
/* Create a name and save it in the validation database. */
- code = kg_init_name(context, princ, ad_context,
+ code = kg_init_name(context, princ, service, host, ad_context,
KG_INIT_NAME_INTERN | KG_INIT_NAME_NO_COPY, &name);
if (code)
goto cleanup;
princ = NULL;
ad_context = NULL;
+ service = host = NULL;
*output_name = (gss_name_t)name;
status = GSS_S_COMPLETE;
krb5_free_context(context);
free(tmp);
free(tmp2);
+ free(service);
+ free(host);
return status;
}
#include <stdarg.h>
krb5_error_code
-kg_init_name(krb5_context context,
- krb5_principal principal,
- krb5_authdata_context ad_context,
- krb5_flags flags,
- krb5_gss_name_t *ret_name)
+kg_init_name(krb5_context context, krb5_principal principal,
+ char *service, char *host, krb5_authdata_context ad_context,
+ krb5_flags flags, krb5_gss_name_t *ret_name)
{
krb5_error_code code;
krb5_gss_name_t name;
if (code != 0)
goto cleanup;
}
+
+ code = ENOMEM;
+ if (service != NULL) {
+ name->service = strdup(service);
+ if (name->service == NULL)
+ goto cleanup;
+ }
+ if (host != NULL) {
+ name->host = strdup(host);
+ if (name->host == NULL)
+ goto cleanup;
+ }
+ code = 0;
} else {
name->princ = principal;
+ name->service = service;
+ name->host = host;
name->ad_context = ad_context;
}
if (flags & KG_INIT_NAME_INTERN)
kg_delete_name((gss_name_t)*name);
krb5_free_principal(context, (*name)->princ);
+ free((*name)->service);
+ free((*name)->host);
krb5_authdata_context_free(context, (*name)->ad_context);
k5_mutex_destroy(&(*name)->lock);
free(*name);
if (code != 0)
return code;
- code = kg_init_name(context, src->princ,
+ code = kg_init_name(context, src->princ, src->service, src->host,
src->ad_context, flags, dst);
k5_mutex_unlock(&src->lock);
return krb5_principal_compare(context, name1->princ, name2->princ);
}
+/* Determine the principal to use for an acceptor name, which is different from
+ * name->princ for host-based names. */
+krb5_boolean
+kg_acceptor_princ(krb5_context context, krb5_gss_name_t name,
+ krb5_principal *princ_out)
+{
+ krb5_error_code code;
+ const char *host;
+ char *tmp = NULL;
+
+ *princ_out = NULL;
+ if (name == NULL)
+ return 0;
+
+ /* If it's not a host-based name, just copy name->princ. */
+ if (name->service == NULL)
+ return krb5_copy_principal(context, name->princ, princ_out);
+
+ if (name->host != NULL && name->princ->length == 2) {
+ /* If a host was given, we have to use the canonicalized form of it (as
+ * given by krb5_sname_to_principal) for backward compatibility. */
+ const krb5_data *d = &name->princ->data[1];
+ tmp = k5alloc(d->length + 1, &code);
+ if (tmp == NULL)
+ return ENOMEM;
+ memcpy(tmp, d->data, d->length);
+ tmp[d->length] = '\0';
+ host = tmp;
+ } else /* No host was given; use an empty string. */
+ host = "";
+
+ code = krb5_build_principal(context, princ_out, 0, "", name->service, host,
+ (char *)NULL);
+ if (code == 0)
+ (*princ_out)->type = KRB5_NT_SRV_HST;
+ free(tmp);
+ return code;
+}
+
static OM_uint32
kg_map_name_error(OM_uint32 *minor_status, krb5_error_code code)
{
cred->tgt_expire = impersonator_cred->tgt_expire;
- code = kg_init_name(context, subject_creds->client, NULL, 0, &cred->name);
+ code = kg_init_name(context, subject_creds->client, NULL, NULL, NULL, 0,
+ &cred->name);
if (code != 0)
goto cleanup;
(krb5_pointer *) &princ,
&bp, &remain);
if (kret == 0) {
- kret = kg_init_name(kcontext, princ, NULL,
+ kret = kg_init_name(kcontext, princ, NULL, NULL, NULL,
KG_INIT_NAME_NO_COPY, &ctx->here);
if (kret)
krb5_free_principal(kcontext, princ);
(krb5_pointer *) &princ,
&bp, &remain);
if (kret == 0) {
- kret = kg_init_name(kcontext, princ, NULL,
+ kret = kg_init_name(kcontext, princ, NULL, NULL, NULL,
KG_INIT_NAME_NO_COPY, &ctx->there);
if (kret)
krb5_free_principal(kcontext, princ);
ser_princ.o \
serialize.o \
set_realm.o \
+ sname_match.o \
srv_dec_tkt.o \
srv_rcache.o \
str_conv.o \
$(OUTPRE)ser_princ.$(OBJEXT) \
$(OUTPRE)serialize.$(OBJEXT) \
$(OUTPRE)set_realm.$(OBJEXT) \
+ $(OUTPRE)sname_match.$(OBJEXT) \
$(OUTPRE)srv_dec_tkt.$(OBJEXT) \
$(OUTPRE)srv_rcache.$(OBJEXT) \
$(OUTPRE)str_conv.$(OBJEXT) \
$(srcdir)/ser_princ.c \
$(srcdir)/serialize.c \
$(srcdir)/set_realm.c \
+ $(srcdir)/sname_match.c \
$(srcdir)/srv_dec_tkt.c \
$(srcdir)/srv_rcache.c \
$(srcdir)/str_conv.c \
goto cleanup;
ctx->allow_weak_crypto = tmp;
+ retval = profile_get_boolean(ctx->profile, KRB5_CONF_LIBDEFAULTS,
+ KRB5_CONF_IGNORE_ACCEPTOR_HOSTNAME, NULL, 0,
+ &tmp);
+ if (retval)
+ goto cleanup;
+ ctx->ignore_acceptor_hostname = tmp;
+
/* initialize the prng (not well, but passable) */
if ((retval = krb5_c_random_os_entropy( ctx, 0, NULL)) !=0)
goto cleanup;
int permitted_etypes_len,
krb5_enctype *negotiated_etype);
+/* Return true if princ might match multiple principals. */
+static inline krb5_boolean
+is_matching(krb5_context context, krb5_const_principal princ)
+{
+ if (princ == NULL)
+ return TRUE;
+ return (princ->type == KRB5_NT_SRV_HST && princ->length == 2
+ && (princ->realm.length == 0 || princ->data[1].length == 0 ||
+ context->ignore_acceptor_hostname));
+}
+
+/* Decrypt the ticket in req using the key in ent. */
static krb5_error_code
-rd_req_decrypt_tkt_part(krb5_context context, const krb5_ap_req *req,
- krb5_const_principal server, krb5_keytab keytab,
- krb5_keyblock *key)
+try_one_entry(krb5_context context, const krb5_ap_req *req,
+ krb5_keytab_entry *ent, krb5_keyblock *keyblock_out)
{
- krb5_error_code retval;
- krb5_keytab_entry ktent;
+ krb5_error_code ret;
+ krb5_principal tmp = NULL;
+
+ /* Try decrypting the ticket with this entry's key. */
+ ret = krb5_decrypt_tkt_part(context, &ent->key, req->ticket);
+ if (ret)
+ return ret;
+
+ /* Make a copy of the principal for the ticket server field. */
+ ret = krb5_copy_principal(context, ent->principal, &tmp);
+ if (ret)
+ return ret;
+
+ /* Make a copy of the decrypting key if requested by the caller. */
+ if (keyblock_out != NULL) {
+ ret = krb5_copy_keyblock_contents(context, &ent->key, keyblock_out);
+ if (ret) {
+ krb5_free_principal(context, tmp);
+ return ret;
+ }
+ }
- retval = KRB5_KT_NOTFOUND;
+ /* Make req->ticket->server indicate the actual server principal. */
+ krb5_free_principal(context, req->ticket->server);
+ req->ticket->server = tmp;
-#ifndef LEAN_CLIENT
- if (server != NULL || keytab->ops->start_seq_get == NULL) {
- retval = krb5_kt_get_entry(context, keytab,
- server != NULL ? server : req->ticket->server,
- req->ticket->enc_part.kvno,
- req->ticket->enc_part.enctype, &ktent);
- if (retval == 0) {
- retval = krb5_decrypt_tkt_part(context, &ktent.key, req->ticket);
- if (retval == 0) {
- TRACE_RD_REQ_DECRYPT_SPECIFIC(context, ktent.principal,
- &ktent.key);
- }
- if (retval == 0 && key != NULL)
- retval = krb5_copy_keyblock_contents(context, &ktent.key, key);
+ return 0;
+}
- (void) krb5_free_keytab_entry_contents(context, &ktent);
- }
- } else {
- krb5_error_code code;
- krb5_kt_cursor cursor;
+/* Decrypt the ticket in req using a principal looked up from keytab. */
+static krb5_error_code
+try_one_princ(krb5_context context, const krb5_ap_req *req,
+ krb5_const_principal princ, krb5_keytab keytab,
+ krb5_keyblock *keyblock_out)
+{
+ krb5_error_code ret;
+ krb5_keytab_entry ent;
+
+ ret = krb5_kt_get_entry(context, keytab, princ,
+ req->ticket->enc_part.kvno,
+ req->ticket->enc_part.enctype, &ent);
+ if (ret)
+ return ret;
+ ret = try_one_entry(context, req, &ent, keyblock_out);
+ (void)krb5_free_keytab_entry_contents(context, &ent);
+ if (ret)
+ return ret;
+
+ TRACE_RD_REQ_DECRYPT_SPECIFIC(context, ent.principal, &ent.key);
+ return 0;
+}
- code = krb5_kt_start_seq_get(context, keytab, &cursor);
- if (code != 0) {
- retval = code;
- goto map_error;
- }
+/*
+ * Decrypt the ticket in req using an entry in keytab matching server (if
+ * given). Set req->ticket->server to the principal of the keytab entry used.
+ * Store the decrypting key in *keyblock_out if it is not NULL.
+ */
+static krb5_error_code
+decrypt_ticket(krb5_context context, const krb5_ap_req *req,
+ krb5_const_principal server, krb5_keytab keytab,
+ krb5_keyblock *keyblock_out)
+{
+ krb5_error_code ret;
+ krb5_keytab_entry ent;
+ krb5_kt_cursor cursor;
- while ((code = krb5_kt_next_entry(context, keytab,
- &ktent, &cursor)) == 0) {
- if (ktent.key.enctype != req->ticket->enc_part.enctype) {
- (void) krb5_free_keytab_entry_contents(context, &ktent);
- continue;
- }
+#ifdef LEAN_CLIENT
+ return KRB5KRB_AP_WRONG_PRINC;
+#else
+ /* If we have an explicit server principal, try just that one. */
+ if (!is_matching(context, server))
+ return try_one_princ(context, req, server, keytab, keyblock_out);
+
+ if (keytab->ops->start_seq_get == NULL) {
+ /* We can't iterate over the keytab. Try the principal asserted by the
+ * client if it's allowed by the server parameter. */
+ if (!krb5_sname_match(context, server, req->ticket->server))
+ return KRB5KRB_AP_WRONG_PRINC;
+ return try_one_princ(context, req, req->ticket->server, keytab,
+ keyblock_out);
+ }
- retval = krb5_decrypt_tkt_part(context, &ktent.key,
- req->ticket);
-
- if (retval == 0) {
- krb5_principal tmp = NULL;
-
- TRACE_RD_REQ_DECRYPT_ANY(context, ktent.principal, &ktent.key);
- /*
- * We overwrite ticket->server to be the principal
- * that we match in the keytab. The reason for doing
- * this is that GSS-API and other consumers look at
- * that principal to make authorization decisions
- * about whether the appropriate server is contacted.
- * It might be cleaner to create a new API and store
- * the server in the auth_context, but doing so would
- * probably miss existing uses of the server. Instead,
- * perhaps an API should be created to retrieve the
- * server as it appeared in the ticket.
- */
- retval = krb5_copy_principal(context, ktent.principal, &tmp);
- if (retval == 0 && key != NULL)
- retval = krb5_copy_keyblock_contents(context, &ktent.key, key);
- if (retval == 0) {
- krb5_free_principal(context, req->ticket->server);
- req->ticket->server = tmp;
- } else {
- krb5_free_principal(context, tmp);
- }
- (void) krb5_free_keytab_entry_contents(context, &ktent);
+ ret = krb5_kt_start_seq_get(context, keytab, &cursor);
+ if (ret)
+ goto cleanup;
+
+ while ((ret = krb5_kt_next_entry(context, keytab, &ent, &cursor)) == 0) {
+ if (ent.key.enctype == req->ticket->enc_part.enctype &&
+ krb5_sname_match(context, server, ent.principal)) {
+ ret = try_one_entry(context, req, &ent, keyblock_out);
+ if (ret == 0) {
+ TRACE_RD_REQ_DECRYPT_ANY(context, ent.principal, &ent.key);
+ (void)krb5_free_keytab_entry_contents(context, &ent);
break;
}
- (void) krb5_free_keytab_entry_contents(context, &ktent);
}
- code = krb5_kt_end_seq_get(context, keytab, &cursor);
- if (code != 0)
- retval = code;
+ (void)krb5_free_keytab_entry_contents(context, &ent);
}
-#endif /* LEAN_CLIENT */
-map_error:
- switch (retval) {
+ (void)krb5_kt_end_seq_get(context, keytab, &cursor);
+
+cleanup:
+ switch (ret) {
case KRB5_KT_KVNONOTFOUND:
case KRB5_KT_NOTFOUND:
+ case KRB5_KT_END:
case KRB5KRB_AP_ERR_BAD_INTEGRITY:
- retval = KRB5KRB_AP_WRONG_PRINC;
+ ret = KRB5KRB_AP_WRONG_PRINC;
break;
default:
break;
}
- return retval;
+ return ret;
+#endif /* LEAN_CLIENT */
}
#if 0
krb5_ticket **ticket, int check_valid_flag)
{
krb5_error_code retval = 0;
- krb5_principal_data princ_data;
krb5_enctype *desired_etypes = NULL;
int desired_etypes_len = 0;
int rfc4537_etypes_len = 0;
decrypt_key.enctype = ENCTYPE_NULL;
decrypt_key.contents = NULL;
-
req->ticket->enc_part2 = NULL;
- if (server && krb5_is_referral_realm(&server->realm)) {
- char *realm;
- princ_data = *server;
- server = &princ_data;
- retval = krb5_get_default_realm(context, &realm);
- if (retval)
- return retval;
- princ_data.realm.data = realm;
- princ_data.realm.length = strlen(realm);
- }
-
/* if (req->ap_options & AP_OPTS_USE_SESSION_KEY)
do we need special processing here ? */
krb5_k_free_key(context, (*auth_context)->key);
(*auth_context)->key = NULL;
} else {
- if ((retval = rd_req_decrypt_tkt_part(context, req,
- server, keytab,
- check_valid_flag ? &decrypt_key : NULL)))
+ retval = decrypt_ticket(context, req, server, keytab,
+ check_valid_flag ? &decrypt_key : NULL);
+ if (retval)
goto cleanup;
}
TRACE_RD_REQ_TICKET(context, req->ticket->enc_part2->client,
if (permitted_etypes != NULL &&
permitted_etypes != (*auth_context)->permitted_etypes)
free(permitted_etypes);
- if (server == &princ_data)
- krb5_free_default_realm(context, princ_data.realm.data);
if (retval) {
/* only free if we're erroring out...otherwise some
applications will need the output. */
--- /dev/null
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/krb/sname_match.c - krb5_sname_match API function */
+/*
+ * Copyright (C) 2011 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ */
+
+#include "k5-int.h"
+
+krb5_boolean KRB5_CALLCONV
+krb5_sname_match(krb5_context context, krb5_const_principal matching,
+ krb5_const_principal princ)
+{
+ if (matching == NULL)
+ return TRUE;
+
+ if (matching->type != KRB5_NT_SRV_HST || matching->length != 2)
+ return krb5_principal_compare(context, matching, princ);
+
+ /* Check the realm if present in matching. */
+ if (matching->realm.length != 0 && !data_eq(matching->realm, princ->realm))
+ return FALSE;
+
+ /* Check the service name (must be present in matching). */
+ if (!data_eq(matching->data[0], princ->data[0]))
+ return FALSE;
+
+ /* Check the hostname if present in matching and not ignored. */
+ if (matching->data[1].length != 0 && !context->ignore_acceptor_hostname &&
+ !data_eq(matching->data[1], princ->data[1]))
+ return FALSE;
+
+ /* All elements match. */
+ return TRUE;
+}
krb5_size_opaque
krb5_skdc_timeout_1
krb5_skdc_timeout_shift
+krb5_sname_match
krb5_sname_to_principal
krb5_string_to_deltat
krb5_string_to_salttype
krb5_sendto_kdc @381 ; PRIVATE GSSAPI
krb5int_copy_data_contents_add0 @382 ; PRIVATE GSSAPI
krb5int_free_data_list @383 ; PRIVATE GSSAPI
+
+; new in 1.10
+ krb5_sname_match @384
PROG_LIBPATH=-L$(TOPLIBD)
PROG_RPATH=$(KRB5_LIBDIR)
-SRCS= $(srcdir)/t_imp_name.c $(srcdir)/t_s4u.c $(srcdir)/t_namingexts.c $(srcdir)/t_gssexts.c $(srcdir)/t_saslname.c
+SRCS= $(srcdir)/t_accname.c $(srcdir)/t_imp_name.c $(srcdir)/t_s4u.c \
+ $(srcdir)/t_namingexts.c $(srcdir)/t_gssexts.c $(srcdir)/t_saslname.c
-OBJS= t_imp_name.o t_s4u.o t_namingexts.o t_gssexts.o t_spnego.o t_saslname.o
+OBJS= t_accname.o t_imp_name.o t_s4u.o t_namingexts.o t_gssexts.o \
+ t_spnego.o t_saslname.o
-all:: t_imp_name t_s4u t_namingexts t_gssexts t_spnego t_saslname
+all:: t_accname t_imp_name t_s4u t_namingexts t_gssexts t_spnego t_saslname
-check-pytests:: t_spnego
+check-pytests:: t_accname t_spnego
$(RUNPYTEST) $(srcdir)/t_gssapi.py $(PYTESTFLAGS)
+t_accname: t_accname.o $(GSS_DEPLIBS) $(KRB5_BASE_DEPLIBS)
+ $(CC_LINK) -o t_accname t_accname.o $(GSS_LIBS) $(KRB5_BASE_LIBS)
t_imp_name: t_imp_name.o $(GSS_DEPLIBS) $(KRB5_BASE_DEPLIBS)
$(CC_LINK) -o t_imp_name t_imp_name.o $(GSS_LIBS) $(KRB5_BASE_LIBS)
t_s4u: t_s4u.o $(GSS_DEPLIBS) $(KRB5_BASE_DEPLIBS)
--- /dev/null
+/* -*- mode: c; indent-tabs-mode: nil -*- */
+/*
+ * Copyright 2011 by the Massachusetts Institute of Technology.
+ * All Rights Reserved.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <gssapi/gssapi_krb5.h>
+
+/*
+ * Test program for acceptor names, intended to be run from a Python test
+ * script. Performs a one-token gss_init_sec_context/gss_accept_sec_context
+ * exchange with the default initiator name, a specified principal name as
+ * target name, and a specified host-based name as acceptor name (or
+ * GSS_C_NO_NAME if no acceptor name is given). If the exchange is successful,
+ * queries the context for the acceptor name and prints it. If any call is
+ * unsuccessful, displays an error message. Exits with status 0 if all
+ * operations are successful, or 1 if not.
+ *
+ * Usage: ./t_accname targetname [acceptorname]
+ */
+
+static void
+display_status_1(const char *m, OM_uint32 code, int type)
+{
+ OM_uint32 maj_stat, min_stat;
+ gss_buffer_desc msg;
+ OM_uint32 msg_ctx;
+
+ msg_ctx = 0;
+ while (1) {
+ maj_stat = gss_display_status(&min_stat, code,
+ type, GSS_C_NULL_OID,
+ &msg_ctx, &msg);
+ fprintf(stderr, "%s: %s\n", m, (char *)msg.value);
+ (void) gss_release_buffer(&min_stat, &msg);
+
+ if (!msg_ctx)
+ break;
+ }
+}
+
+static void
+display_status(const char *msg, OM_uint32 maj_stat, OM_uint32 min_stat)
+{
+ display_status_1(msg, maj_stat, GSS_C_GSS_CODE);
+ display_status_1(msg, min_stat, GSS_C_MECH_CODE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ OM_uint32 minor, major;
+ gss_cred_id_t acceptor_cred;
+ gss_buffer_desc buf;
+ gss_name_t target_name, acceptor_name = GSS_C_NO_NAME, real_acceptor_name;
+ gss_buffer_desc token, tmp, namebuf;
+ gss_ctx_id_t initiator_context = GSS_C_NO_CONTEXT;
+ gss_ctx_id_t acceptor_context = GSS_C_NO_CONTEXT;
+
+ if (argc < 2 || argc > 3) {
+ fprintf(stderr, "Usage: %s targetname [acceptorname]\n", argv[0]);
+ return 1;
+ }
+
+ /* Import the target name as a krb5 principal name. */
+ buf.value = argv[1];
+ buf.length = strlen((char *)buf.value);
+ major = gss_import_name(&minor, &buf, (gss_OID)GSS_KRB5_NT_PRINCIPAL_NAME,
+ &target_name);
+ if (GSS_ERROR(major)) {
+ display_status("gss_import_name(target_name)", major, minor);
+ return 1;
+ }
+
+ /* Import the acceptor name as a host-based name. */
+ if (argc >= 3) {
+ buf.value = argv[2];
+ buf.length = strlen((char *)buf.value);
+ major = gss_import_name(&minor, &buf,
+ (gss_OID)GSS_C_NT_HOSTBASED_SERVICE,
+ &acceptor_name);
+ if (GSS_ERROR(major)) {
+ display_status("gss_import_name(acceptor_name)", major, minor);
+ return 1;
+ }
+ }
+
+ /* Get acceptor cred. */
+ major = gss_acquire_cred(&minor, acceptor_name, GSS_C_INDEFINITE,
+ GSS_C_NO_OID_SET, GSS_C_ACCEPT,
+ &acceptor_cred, NULL, NULL);
+ if (GSS_ERROR(major)) {
+ display_status("gss_acquire_cred", major, minor);
+ return 1;
+ }
+
+ /* Create krb5 initiator context and get the first token. */
+ token.value = NULL;
+ token.length = 0;
+ major = gss_init_sec_context(&minor, GSS_C_NO_CREDENTIAL,
+ &initiator_context, target_name,
+ (gss_OID)gss_mech_krb5,
+ GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG,
+ GSS_C_INDEFINITE, GSS_C_NO_CHANNEL_BINDINGS,
+ GSS_C_NO_BUFFER, NULL, &token, NULL, NULL);
+ if (GSS_ERROR(major)) {
+ display_status("gss_init_sec_context", major, minor);
+ return 1;
+ }
+
+ /* Pass the token to gss_accept_sec_context. */
+ tmp.value = NULL;
+ tmp.length = 0;
+ major = gss_accept_sec_context(&minor, &acceptor_context, acceptor_cred,
+ &token, GSS_C_NO_CHANNEL_BINDINGS,
+ NULL, NULL, &tmp, NULL, NULL, NULL);
+ if (major != GSS_S_COMPLETE) {
+ display_status("gss_accept_sec_context", major, minor);
+ return 1;
+ }
+
+ major = gss_inquire_context(&minor, acceptor_context, NULL,
+ &real_acceptor_name, NULL, NULL, NULL, NULL,
+ NULL);
+ if (GSS_ERROR(major)) {
+ display_status("gss_inquire_context", major, minor);
+ return 1;
+ }
+
+ namebuf.value = NULL;
+ namebuf.length = 0;
+ major = gss_display_name(&minor, real_acceptor_name, &namebuf, NULL);
+ if (GSS_ERROR(major)) {
+ display_status("gss_display_name", major, minor);
+ return 1;
+ }
+
+ printf("%.*s\n", (int)namebuf.length, (char *)namebuf.value);
+
+ (void)gss_release_name(&minor, &target_name);
+ (void)gss_release_name(&minor, &acceptor_name);
+ (void)gss_release_cred(&minor, &acceptor_cred);
+ (void)gss_delete_sec_context(&minor, &initiator_context, NULL);
+ (void)gss_delete_sec_context(&minor, &acceptor_context, NULL);
+ (void)gss_release_buffer(&minor, &token);
+ (void)gss_release_buffer(&minor, &tmp);
+ return 0;
+}
#!/usr/bin/python
from k5test import *
+# Test krb5 negotiation under SPNEGO for all enctype configurations.
for realm in multipass_realms():
realm.run_as_client(['./t_spnego', realm.host_princ, realm.keytab])
-success('GSSAPI test programs (SPNEGO only).')
+### Test acceptor name behavior.
+
+realm = K5Realm(start_kadmind=False)
+
+# Create some host-based principals and put most of them into the
+# keytab. Rename one principal so that the keytab name matches the
+# key but not the client name.
+realm.run_kadminl('addprinc -randkey service1/abraham')
+realm.run_kadminl('addprinc -randkey service1/barack')
+realm.run_kadminl('addprinc -randkey service2/calvin')
+realm.run_kadminl('addprinc -randkey service2/dwight')
+realm.run_kadminl('addprinc -randkey host/-nomatch-')
+realm.run_kadminl('xst service1/abraham')
+realm.run_kadminl('xst service1/barack')
+realm.run_kadminl('xst service2/calvin')
+realm.run_kadminl('renprinc -force service1/abraham service1/andrew')
+
+# Test with no acceptor name, including client/keytab principal
+# mismatch (non-fatal) and missing keytab entry (fatal).
+output = realm.run_as_client(['./t_accname', 'service1/andrew'])
+if 'service1/abraham' not in output:
+ fail('Expected service1/abraham in t_accname output')
+output = realm.run_as_client(['./t_accname', 'service1/barack'])
+if 'service1/barack' not in output:
+ fail('Expected service1/barack in t_accname output')
+output = realm.run_as_client(['./t_accname', 'service2/calvin'])
+if 'service2/calvin' not in output:
+ fail('Expected service1/barack in t_accname output')
+output = realm.run_as_client(['./t_accname', 'service2/dwight'],
+ expected_code=1)
+if 'Wrong principal in request' not in output:
+ fail('Expected error message not seen in t_accname output')
+
+# Test with acceptor name containing service only, including
+# client/keytab hostname mismatch (non-fatal) and service name
+# mismatch (fatal).
+output = realm.run_as_client(['./t_accname', 'service1/andrew', 'service1'])
+if 'service1/abraham' not in output:
+ fail('Expected service1/abraham in t_accname output')
+output = realm.run_as_client(['./t_accname', 'service1/andrew', 'service2'],
+ expected_code=1)
+if 'Wrong principal in request' not in output:
+ fail('Expected error message not seen in t_accname output')
+output = realm.run_as_client(['./t_accname', 'service2/calvin', 'service2'])
+if 'service2/calvin' not in output:
+ fail('Expected service2/calvin in t_accname output')
+output = realm.run_as_client(['./t_accname', 'service2/calvin', 'service1'],
+ expected_code=1)
+if 'Wrong principal in request' not in output:
+ fail('Expected error message not seen in t_accname output')
+
+# Test with acceptor name containing service and host. Use the
+# client's un-canonicalized hostname as acceptor input to mirror what
+# many servers do.
+output = realm.run_as_client(['./t_accname', realm.host_princ,
+ 'host@%s' % socket.gethostname()])
+if realm.host_princ not in output:
+ fail('Expected %s in t_accname output' % realm.host_princ)
+output = realm.run_as_client(['./t_accname', 'host/-nomatch-',
+ 'host@%s' % socket.gethostname()],
+ expected_code=1)
+if 'Wrong principal in request' not in output:
+ fail('Expected error message not seen in t_accname output')
+
+realm.stop()
+
+# Re-run that last test with ignore_acceptor_hostname set and the
+# principal for the mismatching hostname in the keytab.
+ignore_conf = { 'all' : { 'libdefaults' : {
+ 'ignore_acceptor_hostname' : 'true' } } }
+realm = K5Realm(krb5_conf=ignore_conf, start_kadmind=False)
+realm.run_kadminl('addprinc -randkey host/-nomatch-')
+realm.run_kadminl('xst host/-nomatch-')
+output = realm.run_as_client(['./t_accname', 'host/-nomatch-',
+ 'host@%s' % socket.gethostname()])
+if 'host/-nomatch-' not in output:
+ fail('Expected error message not seen in t_accname output')
+
+success('GSSAPI tests.')