TRACE(c, "Attempting password change; {int} tries remaining", tries)
#define TRACE_GIC_PWD_EXPIRED(c) \
TRACE(c, "Principal expired; getting changepw ticket")
-#define TRACE_GIC_PWD_PRIMARY(c) \
- TRACE(c, "Retrying AS request with primary KDC")
#define TRACE_GSS_CLIENT_KEYTAB_FAIL(c, ret) \
TRACE(c, "Unable to resolve default client keytab: {kerr}", ret)
#define TRACE_INIT_CREDS_PREAUTH_TRYAGAIN(c, patype, code) \
TRACE(c, "Recovering from KDC error {int} using preauth mech {patype}", \
patype, (int)code)
+#define TRACE_INIT_CREDS_PRIMARY(c) \
+ TRACE(c, "Retrying AS request with primary KDC")
#define TRACE_INIT_CREDS_RESTART_FAST(c) \
TRACE(c, "Restarting to upgrade to FAST")
#define TRACE_INIT_CREDS_RESTART_PREAUTH_FAILED(c) \
rlm, (primary) ? " (primary)" : "", (tcp) ? " (tcp only)" : "")
#define TRACE_SENDTO_KDC_K5TLS_LOAD_ERROR(c, ret) \
TRACE(c, "Error loading k5tls module: {kerr}", ret)
-#define TRACE_SENDTO_KDC_PRIMARY(c, primary) \
- TRACE(c, "Response was{str} from primary KDC", (primary) ? "" : " not")
#define TRACE_SENDTO_KDC_RESOLVING(c, hostname) \
TRACE(c, "Resolving hostname {str}", hostname)
#define TRACE_SENDTO_KDC_RESPONSE(c, len, raddr) \
#include "k5-int.h"
#include "int-proto.h"
+#include "os-proto.h"
#include "fast.h"
static krb5_error_code
krb5_timestamp timestamp;
krb5_int32 nonce;
krb5_keyblock *subkey = NULL;
- int tcp_only = 0, use_primary = 0;
+ int no_udp = 0;
struct krb5int_fast_request_state *fast_state = NULL;
request_data.data = NULL;
goto cleanup;
send_again:
- use_primary = 0;
- retval = krb5_sendto_kdc(context, &request_data, &in_cred->server->realm,
- &response_data, &use_primary, tcp_only);
+ retval = k5_sendto_kdc(context, &request_data, &in_cred->server->realm,
+ FALSE, no_udp, &response_data, NULL);
if (retval == 0) {
if (krb5_is_krb_error(&response_data)) {
- if (!tcp_only) {
+ if (!no_udp) {
krb5_error *err_reply;
retval = decode_krb5_error(&response_data, &err_reply);
if (retval != 0)
if (retval)
goto cleanup;
if (err_reply->error == KRB_ERR_RESPONSE_TOO_BIG) {
- tcp_only = 1;
+ no_udp = 1;
krb5_free_error(context, err_reply);
krb5_free_data_contents(context, &response_data);
goto send_again;
krb5_data request = empty_data(), reply = empty_data();
krb5_data realm = empty_data();
unsigned int flags = 0;
- int tcp_only = 0, use_primary;
+ int no_udp = 0;
for (;;) {
/* Get the next request and realm. Turn on TCP if necessary. */
code = krb5_tkt_creds_step(context, ctx, &reply, &request, &realm,
&flags);
- if (code == KRB5KRB_ERR_RESPONSE_TOO_BIG && !tcp_only) {
+ if (code == KRB5KRB_ERR_RESPONSE_TOO_BIG && !no_udp) {
TRACE_TKT_CREDS_RETRY_TCP(context);
- tcp_only = 1;
+ no_udp = 1;
} else if (code != 0 || !(flags & KRB5_TKT_CREDS_STEP_FLAG_CONTINUE))
break;
krb5_free_data_contents(context, &reply);
/* Send it to a KDC for the appropriate realm. */
- use_primary = 0;
- code = krb5_sendto_kdc(context, &request, &realm,
- &reply, &use_primary, tcp_only);
+ code = k5_sendto_kdc(context, &request, &realm, FALSE, no_udp,
+ &reply, NULL);
if (code != 0)
break;
#include "k5-int.h"
#include "fast.h"
#include "init_creds_ctx.h"
+#include "os-proto.h"
/* Extract etype info from the error message pkt into icc, if it is a
* PREAUTH_REQUIRED error. Otherwise return the protocol error code. */
krb5_data reply = empty_data(), req = empty_data(), realm = empty_data();
krb5_data salt = empty_data(), s2kparams = empty_data();
unsigned int flags;
- int primary, tcp_only;
+ int no_udp;
krb5_error_code ret;
*enctype_out = ENCTYPE_NULL;
}
/* Send the packet (possibly once with UDP and again with TCP). */
- tcp_only = 0;
+ no_udp = 0;
for (;;) {
- primary = 0;
- ret = krb5_sendto_kdc(context, &req, &realm, &reply, &primary,
- tcp_only);
+ ret = k5_sendto_kdc(context, &req, &realm, FALSE, no_udp, &reply,
+ NULL);
if (ret)
goto cleanup;
if (krb5_is_krb_error(&reply)) {
ret = get_from_error(context, &reply, icc);
if (ret) {
- if (!tcp_only && ret == KRB5KRB_ERR_RESPONSE_TOO_BIG) {
- tcp_only = 1;
+ if (!no_udp && ret == KRB5KRB_ERR_RESPONSE_TOO_BIG) {
+ no_udp = 1;
krb5_free_data_contents(context, &reply);
continue;
}
krb5_error_code
k5_init_creds_get(krb5_context context, krb5_init_creds_context ctx,
- int *use_primary)
+ krb5_boolean use_primary, struct kdclist *kdcs)
{
krb5_error_code code;
krb5_data request;
krb5_data reply;
krb5_data realm;
unsigned int flags = 0;
- int tcp_only = 0, primary = *use_primary;
+ int no_udp = 0;
request.length = 0;
request.data = NULL;
&request,
&realm,
&flags);
- if (code == KRB5KRB_ERR_RESPONSE_TOO_BIG && !tcp_only) {
+ if (code == KRB5KRB_ERR_RESPONSE_TOO_BIG && !no_udp) {
TRACE_INIT_CREDS_RETRY_TCP(context);
- tcp_only = 1;
+ no_udp = 1;
} else if (code != 0 || !(flags & KRB5_INIT_CREDS_STEP_FLAG_CONTINUE))
break;
krb5_free_data_contents(context, &reply);
- primary = *use_primary;
- code = krb5_sendto_kdc(context, &request, &realm,
- &reply, &primary, tcp_only);
+ code = k5_sendto_kdc(context, &request, &realm, use_primary, no_udp,
+ &reply, kdcs);
if (code != 0)
break;
krb5_free_data_contents(context, &reply);
krb5_free_data_contents(context, &realm);
- *use_primary = primary;
return code;
}
krb5_init_creds_get(krb5_context context,
krb5_init_creds_context ctx)
{
- int use_primary = 0;
-
- return k5_init_creds_get(context, ctx, &use_primary);
+ return k5_init_creds_get(context, ctx, FALSE, NULL);
}
krb5_error_code KRB5_CALLCONV
return code;
}
-krb5_error_code KRB5_CALLCONV
-k5_get_init_creds(krb5_context context, krb5_creds *creds,
- krb5_principal client, krb5_prompter_fct prompter,
- void *prompter_data, krb5_deltat start_time,
- const char *in_tkt_service, krb5_get_init_creds_opt *options,
- get_as_key_fn gak_fct, void *gak_data, int *use_primary,
- krb5_kdc_rep **as_reply)
+static krb5_error_code
+try_init_creds(krb5_context context, krb5_creds *creds, krb5_principal client,
+ krb5_prompter_fct prompter, void *prompter_data,
+ krb5_deltat start_time, const char *in_tkt_service,
+ krb5_get_init_creds_opt *options, get_as_key_fn gak_fct,
+ void *gak_data, krb5_boolean use_primary, struct kdclist *kdcs,
+ krb5_kdc_rep **as_reply)
{
krb5_error_code code;
krb5_init_creds_context ctx = NULL;
goto cleanup;
}
- code = k5_init_creds_get(context, ctx, use_primary);
+ code = k5_init_creds_get(context, ctx, use_primary, kdcs);
if (code != 0)
goto cleanup;
return code;
}
+krb5_error_code
+k5_get_init_creds(krb5_context context, krb5_creds *creds,
+ krb5_principal client, krb5_prompter_fct prompter,
+ void *prompter_data, krb5_deltat start_time,
+ const char *in_tkt_service, krb5_get_init_creds_opt *options,
+ get_as_key_fn gak_fct, void *gak_data,
+ krb5_kdc_rep **as_reply)
+{
+ krb5_error_code ret;
+ struct kdclist *kdcs = NULL;
+ struct errinfo errsave = EMPTY_ERRINFO;
+
+ ret = k5_kdclist_create(&kdcs);
+ if (ret)
+ goto cleanup;
+
+ /* Try getting the requested ticket from any KDC. */
+ ret = try_init_creds(context, creds, client, prompter, prompter_data,
+ start_time, in_tkt_service, options, gak_fct,
+ gak_data, FALSE, kdcs, as_reply);
+ if (!ret)
+ goto cleanup;
+
+ /* If all of the KDCs are unavailable, or if the error was due to a user
+ * interrupt, fail. */
+ if (ret == KRB5_KDC_UNREACH || ret == KRB5_REALM_CANT_RESOLVE ||
+ ret == KRB5_LIBOS_PWDINTR || ret == KRB5_LIBOS_CANTREADPWD)
+ goto cleanup;
+
+ /* If any reply came from a replica, try again with only primary KDCs. */
+ if (k5_kdclist_any_replicas(context, kdcs)) {
+ k5_save_ctx_error(context, ret, &errsave);
+ TRACE_INIT_CREDS_PRIMARY(context);
+ ret = try_init_creds(context, creds, client, prompter, prompter_data,
+ start_time, in_tkt_service, options, gak_fct,
+ gak_data, TRUE, NULL, as_reply);
+ if (ret == KRB5_KDC_UNREACH || ret == KRB5_REALM_CANT_RESOLVE ||
+ ret == KRB5_REALM_UNKNOWN) {
+ /* We couldn't contact a primary KDC; return the error from the
+ * replica we were able to contact. */
+ ret = k5_restore_ctx_error(context, &errsave);
+ }
+ }
+
+cleanup:
+ k5_kdclist_free(kdcs);
+ k5_clear_error(&errsave);
+ return ret;
+}
+
krb5_error_code
k5_identify_realm(krb5_context context, krb5_principal client,
const krb5_data *subject_cert, krb5_principal *client_out)
krb5_error_code ret;
krb5_get_init_creds_opt *opts = NULL;
krb5_init_creds_context ctx = NULL;
- int use_primary = 0;
*client_out = NULL;
ctx->identify_realm = TRUE;
ctx->subject_cert = subject_cert;
- ret = k5_init_creds_get(context, ctx, &use_primary);
+ ret = k5_init_creds_get(context, ctx, FALSE, NULL);
if (ret)
goto cleanup;
get_init_creds_keytab(krb5_context context, krb5_creds *creds,
krb5_principal client, krb5_keytab keytab,
krb5_deltat start_time, const char *in_tkt_service,
- krb5_get_init_creds_opt *options, int *use_primary)
+ krb5_get_init_creds_opt *options,
+ krb5_boolean use_primary, struct kdclist *kdcs)
{
krb5_error_code ret;
krb5_init_creds_context ctx = NULL;
if (ret != 0)
goto cleanup;
- ret = k5_init_creds_get(context, ctx, use_primary);
+ ret = k5_init_creds_get(context, ctx, use_primary, kdcs);
if (ret != 0)
goto cleanup;
krb5_get_init_creds_opt *options)
{
krb5_error_code ret;
- int use_primary;
krb5_keytab keytab;
struct errinfo errsave = EMPTY_ERRINFO;
+ struct kdclist *kdcs = NULL;
if (arg_keytab == NULL) {
if ((ret = krb5_kt_default(context, &keytab)))
keytab = arg_keytab;
}
- use_primary = 0;
+ ret = k5_kdclist_create(&kdcs);
+ if (ret)
+ goto cleanup;
/* first try: get the requested tkt from any kdc */
ret = get_init_creds_keytab(context, creds, client, keytab, start_time,
- in_tkt_service, options, &use_primary);
+ in_tkt_service, options, FALSE, kdcs);
/* check for success */
/* If the reply did not come from the primary kdc, try again with
* the primary kdc. */
- if (!use_primary) {
- use_primary = 1;
-
+ if (!k5_kdclist_any_replicas(context, kdcs)) {
k5_save_ctx_error(context, ret, &errsave);
ret = get_init_creds_keytab(context, creds, client, keytab,
start_time, in_tkt_service, options,
- &use_primary);
+ TRUE, NULL);
if (ret == 0)
goto cleanup;
cleanup:
if (arg_keytab == NULL)
krb5_kt_close(context, keytab);
+ k5_kdclist_free(kdcs);
k5_clear_error(&errsave);
return(ret);
char * server = NULL;
krb5_keytab keytab;
krb5_principal client_princ, server_princ;
- int use_primary = 0;
retval = k5_populate_gic_opt(context, &opts, options, addrs, ktypes,
pre_auth_types, creds);
client_princ = creds->client;
retval = k5_get_init_creds(context, creds, creds->client,
krb5_prompter_posix, NULL, 0, server, opts,
- get_as_key_keytab, (void *)keytab, &use_primary,
+ get_as_key_keytab, (void *)keytab,
ret_as_reply);
krb5_free_unparsed_name( context, server);
if (retval) {
krb5_get_init_creds_opt *options)
{
krb5_error_code ret;
- int use_primary;
krb5_kdc_rep *as_reply;
int tries;
krb5_creds chpw_creds;
char banner[1024], pw0array[1024], pw1array[1024];
krb5_prompt prompt[2];
krb5_prompt_type prompt_types[sizeof(prompt)/sizeof(prompt[0])];
- struct errinfo errsave = EMPTY_ERRINFO;
char *message;
- use_primary = 0;
as_reply = NULL;
memset(&chpw_creds, 0, sizeof(chpw_creds));
memset(&gakpw, 0, sizeof(gakpw));
gakpw.password = &pw0;
}
- /* first try: get the requested tkt from any kdc */
-
ret = k5_get_init_creds(context, creds, client, prompter, data, start_time,
in_tkt_service, options, krb5_get_as_key_password,
- &gakpw, &use_primary, &as_reply);
-
- /* check for success */
-
- if (ret == 0)
+ &gakpw, &as_reply);
+ if (!ret)
goto cleanup;
- /* If all the kdc's are unavailable, or if the error was due to a
- user interrupt, fail */
-
- if (ret == KRB5_KDC_UNREACH || ret == KRB5_REALM_CANT_RESOLVE ||
- ret == KRB5_LIBOS_PWDINTR || ret == KRB5_LIBOS_CANTREADPWD)
- goto cleanup;
-
- /* If the reply did not come from the primary kdc, try again with
- * the primary kdc. */
-
- if (!use_primary) {
- TRACE_GIC_PWD_PRIMARY(context);
- use_primary = 1;
-
- k5_save_ctx_error(context, ret, &errsave);
- if (as_reply) {
- krb5_free_kdc_rep( context, as_reply);
- as_reply = NULL;
- }
- ret = k5_get_init_creds(context, creds, client, prompter, data,
- start_time, in_tkt_service, options,
- krb5_get_as_key_password, &gakpw, &use_primary,
- &as_reply);
-
- if (ret == 0)
- goto cleanup;
-
- /* If the primary is unreachable, return the error from the replica we
- * were able to contact and reset the use_primary flag. */
- if (ret == KRB5_KDC_UNREACH || ret == KRB5_REALM_CANT_RESOLVE ||
- ret == KRB5_REALM_UNKNOWN) {
- ret = k5_restore_ctx_error(context, &errsave);
- use_primary = 0;
- }
- }
-
- /* at this point, we have an error from the primary. if the error
- is not password expired, or if it is but there's no prompter,
- return this error */
-
- if ((ret != KRB5KDC_ERR_KEY_EXP) ||
- (prompter == NULL))
+ /* If the error is not password expired, or if it is but there's no
+ * prompter, return this error. */
+ if (ret != KRB5KDC_ERR_KEY_EXP || prompter == NULL)
goto cleanup;
/* historically the default has been to prompt for password change.
goto cleanup;
ret = k5_get_init_creds(context, &chpw_creds, client, prompter, data,
start_time, "kadmin/changepw", chpw_opts,
- krb5_get_as_key_password, &gakpw, &use_primary,
- NULL);
+ krb5_get_as_key_password, &gakpw, NULL);
if (ret)
goto cleanup;
if (ret)
goto cleanup;
- /* The password change was successful. Get an initial ticket from the
- * primary. This is the last try. The return from this is final. */
-
+ /* The password change was successful. Try one last time to get an initial
+ * ticket. */
TRACE_GIC_PWD_CHANGED(context);
gakpw.password = &pw0;
ret = k5_get_init_creds(context, creds, client, prompter, data,
start_time, in_tkt_service, options,
- krb5_get_as_key_password, &gakpw, &use_primary,
- &as_reply);
+ krb5_get_as_key_password, &gakpw, &as_reply);
if (ret)
goto cleanup;
krb5_free_cred_contents(context, &chpw_creds);
if (as_reply)
krb5_free_kdc_rep(context, as_reply);
- k5_clear_error(&errsave);
return(ret);
}
krb5_data pw;
char * server;
krb5_principal server_princ, client_princ;
- int use_primary = 0;
krb5_get_init_creds_opt *opts = NULL;
memset(&gakpw, 0, sizeof(gakpw));
client_princ = creds->client;
retval = k5_get_init_creds(context, creds, creds->client,
krb5_prompter_posix, NULL, 0, server, opts,
- krb5_get_as_key_password, &gakpw, &use_primary,
- ret_as_reply);
+ krb5_get_as_key_password, &gakpw, ret_as_reply);
krb5_free_unparsed_name( context, server);
krb5_get_init_creds_opt_free(context, opts);
zapfree(gakpw.storage.data, gakpw.storage.length);
krb5_error_code retval;
char *server;
krb5_principal server_princ, client_princ;
- int use_primary = 0;
krb5_get_init_creds_opt *opts = NULL;
retval = k5_populate_gic_opt(context, &opts, options, addrs, ktypes,
client_princ = creds->client;
retval = k5_get_init_creds(context, creds, creds->client,
krb5_prompter_posix, NULL, 0, server, opts,
- get_as_key_skey, (void *)key, &use_primary,
- ret_as_reply);
+ get_as_key_skey, (void *)key, ret_as_reply);
krb5_free_unparsed_name(context, server);
if (retval)
goto cleanup;
#define KRB5_INT_FUNC_PROTO__
struct krb5int_fast_request_state;
+struct kdclist;
typedef struct k5_response_items_st k5_response_items;
krb5_error_code
k5_init_creds_get(krb5_context context, krb5_init_creds_context ctx,
- int *use_primary);
+ krb5_boolean use_primary, struct kdclist *kdcs);
krb5_error_code
k5_init_creds_current_time(krb5_context context, krb5_init_creds_context ctx,
krb5_keyusage keyusage, const krb5_data *plain,
krb5_enc_data *cipher);
-krb5_error_code KRB5_CALLCONV
+krb5_error_code
k5_get_init_creds(krb5_context context, krb5_creds *creds,
krb5_principal client, krb5_prompter_fct prompter,
void *prompter_data, krb5_deltat start_time,
const char *in_tkt_service, krb5_get_init_creds_opt *options,
- get_as_key_fn gak, void *gak_data, int *primary,
- krb5_kdc_rep **as_reply);
+ get_as_key_fn gak, void *gak_data, krb5_kdc_rep **as_reply);
/*
* Make AS requests with the canonicalize flag set, stopping when we get a
#endif
#define DEFAULT_URI_LOOKUP TRUE
+struct kdclist_entry {
+ krb5_data realm;
+ struct server_entry server;
+};
+
+struct kdclist {
+ size_t count;
+ struct kdclist_entry *list;
+};
+
static int
maybe_use_dns (krb5_context context, const char *name, int defalt)
{
struct server_entry *ent;
for (ent = list->servers; ent < list->servers + list->nservers; ent++) {
+ if (server->port != ent->port)
+ continue;
if (server->hostname != NULL && ent->hostname != NULL &&
strcmp(server->hostname, ent->hostname) == 0)
return TRUE;
return k5_locate_server(context, realm, serverlist, stype, no_udp);
}
+krb5_error_code
+k5_kdclist_create(struct kdclist **kdcs_out)
+{
+ struct kdclist *kdcs;
+
+ *kdcs_out = NULL;
+ kdcs = malloc(sizeof(*kdcs));
+ if (kdcs == NULL)
+ return ENOMEM;
+ kdcs->count = 0;
+ kdcs->list = NULL;
+ *kdcs_out = kdcs;
+ return 0;
+}
+
+krb5_error_code
+k5_kdclist_add(struct kdclist *kdcs, const krb5_data *realm,
+ struct server_entry *server)
+{
+ krb5_error_code ret;
+ struct kdclist_entry *newptr, *ent;
+
+ newptr = realloc(kdcs->list, (kdcs->count + 1) * sizeof(*kdcs->list));
+ if (newptr == NULL)
+ return ENOMEM;
+ kdcs->list = newptr;
+ ent = &kdcs->list[kdcs->count];
+ ret = krb5int_copy_data_contents(NULL, realm, &ent->realm);
+ if (ret)
+ return ret;
+ /* Steal memory ownership from *server. */
+ ent->server = *server;
+ memset(server, 0, sizeof(*server));
+ kdcs->count++;
+ return 0;
+}
+
+/*
+ * If primaries is empty, mark ent as primary (the realm has no primary KDCs
+ * and therefore no KDCs are replicas). Otherwise mark ent according to
+ * whether it is present in primaries. Return true if ent is determined to be
+ * a replica.
+ */
+static krb5_boolean
+mark_entry(struct kdclist_entry *ent, struct serverlist *primaries)
+{
+ if (primaries->nservers == 0) {
+ ent->server.primary = 1;
+ return FALSE;
+ }
+ ent->server.primary = server_list_contains(primaries, &ent->server);
+ return !ent->server.primary;
+}
+
+/* Mark kdcs->list[start] and all entries with the same realm and transport
+ * according to primaries. Stop and return true if a replica is found. */
+static krb5_boolean
+mark_matching_servers(struct kdclist *kdcs, size_t start,
+ struct serverlist *primaries)
+{
+ size_t i;
+ struct kdclist_entry *ent = &kdcs->list[start];
+
+ if (mark_entry(ent, primaries))
+ return TRUE;
+ for (i = start + 1; i < kdcs->count; i++) {
+ if (kdcs->list[i].server.primary == 1)
+ continue;
+ if (kdcs->list[i].server.transport != ent->server.transport)
+ continue;
+ if (!data_eq(kdcs->list[i].realm, ent->realm))
+ continue;
+ if (mark_entry(&kdcs->list[i], primaries))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/* Return true if any entry in kdcs is a replica. May modify the primary
+ * fields of entries in kdcs. */
krb5_boolean
-k5_kdc_is_primary(krb5_context context, const krb5_data *realm,
- struct server_entry *server)
+k5_kdclist_any_replicas(krb5_context context, struct kdclist *kdcs)
{
- struct serverlist list;
+ size_t i;
+ struct kdclist_entry *ent;
+ struct serverlist primaries;
krb5_boolean found;
- if (server->primary != -1)
- return server->primary;
+ /* Check if we already know that any of the KDCs is a replica. */
+ for (i = 0; i < kdcs->count; i++) {
+ if (kdcs->list[i].server.primary == 0)
+ return TRUE;
+ }
+
+ for (i = 0; i < kdcs->count; i++) {
+ ent = &kdcs->list[i];
- if (locate_server(context, realm, &list, locate_service_primary_kdc,
- server->transport) != 0)
- return FALSE;
- found = server_list_contains(&list, server);
- k5_free_serverlist(&list);
- return found;
+ /* Skip this entry if we already know that it's not a replica. */
+ if (ent->server.primary == 1)
+ continue;
+
+ /* Look up the primary KDCs for this entry's realm and transport. Give
+ * up and return false on error. */
+ if (locate_server(context, &ent->realm, &primaries,
+ locate_service_primary_kdc,
+ ent->server.transport) != 0)
+ return FALSE;
+
+ /* Using the list of primaries, determine whether this entry and any
+ * entries with the same realm and transport are replicas. */
+ found = mark_matching_servers(kdcs, i, &primaries);
+
+ k5_free_serverlist(&primaries);
+ if (found)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+void
+k5_kdclist_free(struct kdclist *kdcs)
+{
+ size_t i;
+
+ if (kdcs == NULL)
+ return;
+ for (i = 0; i < kdcs->count; i++) {
+ free(kdcs->list[i].realm.data);
+ free(kdcs->list[i].server.hostname);
+ free(kdcs->list[i].server.uri_path);
+ }
+ free(kdcs->list);
+ free(kdcs);
}
};
#define SERVERLIST_INIT { NULL, 0 }
+struct kdclist;
+
struct remote_address {
k5_transport transport;
int family;
struct serverlist *serverlist,
krb5_boolean get_primaries, krb5_boolean no_udp);
-krb5_boolean k5_kdc_is_primary(krb5_context context, const krb5_data *realm,
+void k5_free_serverlist(struct serverlist *);
+
+/* Create an object for remembering a history of KDCs contacted during an
+ * exchange. */
+krb5_error_code k5_kdclist_create(struct kdclist **kdcs_out);
+
+/* Add a server entry to kdcs. Transfer ownership of memory from *server and
+ * zero it. */
+krb5_error_code k5_kdclist_add(struct kdclist *kdcs, const krb5_data *realm,
struct server_entry *server);
-void k5_free_serverlist(struct serverlist *);
+/* Return true if any KDC entries in kdcs are replicas, looking up realms'
+ * primary KDCs as necessary. */
+krb5_boolean k5_kdclist_any_replicas(krb5_context context,
+ struct kdclist *kdcs);
+
+void k5_kdclist_free(struct kdclist *kdcs);
#ifdef HAVE_NETINET_IN_H
krb5_error_code krb5_unpack_full_ipaddr(krb5_context,
void *),
void *msg_handler_data);
+krb5_error_code k5_sendto_kdc(krb5_context context, const krb5_data *message,
+ const krb5_data *realm, krb5_boolean use_primary,
+ krb5_boolean no_udp, krb5_data *reply_out,
+ struct kdclist *hist);
+
krb5_error_code krb5int_get_fq_local_hostname(char **);
/* The io vector is *not* const here, unlike writev()! */
*/
krb5_error_code
-krb5_sendto_kdc(krb5_context context, const krb5_data *message,
- const krb5_data *realm, krb5_data *reply_out, int *use_primary,
- int no_udp)
+k5_sendto_kdc(krb5_context context, const krb5_data *message,
+ const krb5_data *realm, krb5_boolean use_primary,
+ krb5_boolean no_udp, krb5_data *reply_out, struct kdclist *kdcs)
{
krb5_error_code retval, oldret, err;
struct serverlist servers;
- int server_used;
+ int server_used = -1;
k5_transport_strategy strategy;
krb5_data reply = empty_data(), *hook_message = NULL, *hook_reply = NULL;
* should probably be returned as well.
*/
- TRACE_SENDTO_KDC(context, message->length, realm, *use_primary, no_udp);
+ TRACE_SENDTO_KDC(context, message->length, realm, use_primary, no_udp);
if (!no_udp && context->udp_pref_limit < 0) {
int tmp;
else
strategy = UDP_LAST;
- retval = k5_locate_kdc(context, realm, &servers, *use_primary, no_udp);
+ retval = k5_locate_kdc(context, realm, &servers, use_primary, no_udp);
if (retval)
return retval;
retval, realm, message, &reply,
&hook_reply);
if (oldret && !retval) {
- /*
- * The hook must set a reply if it overrides an error from
- * k5_sendto(). Treat this reply as coming from the primary
- * KDC.
- */
+ /* The hook must set a reply if it overrides an error from
+ * k5_sendto(). */
assert(hook_reply != NULL);
- *use_primary = 1;
}
}
if (retval)
if (hook_reply != NULL) {
*reply_out = *hook_reply;
free(hook_reply);
- } else {
- *reply_out = reply;
- reply = empty_data();
+ goto cleanup;
}
- /* Set use_primary to 1 if we ended up talking to a primary when we didn't
- * explicitly request to. */
- if (*use_primary == 0) {
- *use_primary = k5_kdc_is_primary(context, realm,
- &servers.servers[server_used]);
- TRACE_SENDTO_KDC_PRIMARY(context, *use_primary);
- }
+ *reply_out = reply;
+ reply = empty_data();
+
+ /* Record which KDC we used if the caller asks. */
+ if (kdcs != NULL && server_used != -1)
+ retval = k5_kdclist_add(kdcs, realm, &servers.servers[server_used]);
cleanup:
krb5_free_data(context, hook_message);
return retval;
}
+krb5_error_code
+krb5_sendto_kdc(krb5_context context, const krb5_data *message,
+ const krb5_data *realm, krb5_data *reply_out, int *use_primary,
+ int no_udp)
+{
+ return k5_sendto_kdc(context, message, realm, *use_primary, no_udp,
+ reply_out, NULL);
+}
+
/*
* Notes:
*
$(RUNPYTEST) $(srcdir)/t_u2u.py $(PYTESTFLAGS)
$(RUNPYTEST) $(srcdir)/t_kdcoptions.py $(PYTESTFLAGS)
$(RUNPYTEST) $(srcdir)/t_replay.py $(PYTESTFLAGS)
+ $(RUNPYTEST) $(srcdir)/t_sendto_kdc.py $(PYTESTFLAGS)
clean:
$(RM) adata conccache etinfo forward gcred hist hooks hrealm
--- /dev/null
+from k5test import *
+
+realm = K5Realm(create_host=False)
+
+mark('Fallback to primary KDC')
+
+# Create a replica database and start a KDC.
+conf_rep = {'dbmodules': {'db': {'database_name': '$testdir/db.replica2'}},
+ 'realms': {'$realm': {'kdc_listen': '$port9',
+ 'kdc_tcp_listen': '$port9'}}}
+replica = realm.special_env('replica', True, kdc_conf=conf_rep)
+dumpfile = os.path.join(realm.testdir, 'dump')
+realm.run([kdb5_util, 'dump', dumpfile])
+realm.run([kdb5_util, 'load', dumpfile], env=replica)
+replica_kdc = realm.start_server([krb5kdc, '-n'], 'starting...', env=replica)
+
+# Change the password on the primary.
+realm.run([kadminl, 'cpw', '-pw', 'new', realm.user_princ])
+
+conf_fallback = {'realms': {'$realm': {'kdc': '$hostname:$port9',
+ 'primary_kdc': '$hostname:$port0'}}}
+fallback = realm.special_env('fallback', False, krb5_conf=conf_fallback)
+msgs = ('Retrying AS request with primary KDC',)
+realm.kinit(realm.user_princ, 'new', env=fallback, expected_trace=msgs)
+
+stop_daemon(replica_kdc)
+
+success('sendto_kdc')