diff --git a/kdc/kdc-tester.c b/kdc/kdc-tester.c index ab58129b7..afc833cb8 100644 --- a/kdc/kdc-tester.c +++ b/kdc/kdc-tester.c @@ -383,7 +383,7 @@ eval_kdestroy(heim_dict_t o) */ static void -eval_array_element(heim_object_t o, void *ptr) +eval_array_element(heim_object_t o, void *ptr, int *stop) { eval_object(o); } diff --git a/lib/base/array.c b/lib/base/array.c index 9590bea34..b34f9de48 100644 --- a/lib/base/array.c +++ b/lib/base/array.c @@ -255,8 +255,12 @@ void heim_array_iterate_f(heim_array_t array, void *ctx, heim_array_iterator_f_t fn) { size_t n; - for (n = 0; n < array->len; n++) - fn(array->val[n], ctx); + int stop = 0; + for (n = 0; n < array->len; n++) { + fn(array->val[n], ctx, &stop); + if (stop) + return; + } } #ifdef __BLOCKS__ @@ -268,11 +272,15 @@ heim_array_iterate_f(heim_array_t array, void *ctx, heim_array_iterator_f_t fn) */ void -heim_array_iterate(heim_array_t array, void (^fn)(heim_object_t)) +heim_array_iterate(heim_array_t array, void (^fn)(heim_object_t, int *)) { size_t n; - for (n = 0; n < array->len; n++) - fn(array->val[n]); + int stop = 0; + for (n = 0; n < array->len; n++) { + fn(array->val[n], &stop); + if (stop) + return; + } } #endif @@ -288,8 +296,13 @@ void heim_array_iterate_reverse_f(heim_array_t array, void *ctx, heim_array_iterator_f_t fn) { size_t n; - for (n = array->len; n > 0; n--) - fn(array->val[n - 1], ctx); + int stop = 0; + + for (n = array->len; n > 0; n--) { + fn(array->val[n - 1], ctx, &stop); + if (stop) + return; + } } #ifdef __BLOCKS__ @@ -301,11 +314,15 @@ heim_array_iterate_reverse_f(heim_array_t array, void *ctx, heim_array_iterator_ */ void -heim_array_iterate_reverse(heim_array_t array, void (^fn)(heim_object_t)) +heim_array_iterate_reverse(heim_array_t array, void (^fn)(heim_object_t, int *)) { size_t n; - for (n = array->len; n > 0; n--) - fn(array->val[n - 1]); + int stop = 0; + for (n = array->len; n > 0; n--) { + fn(array->val[n - 1], &stop); + if (stop) + return; + } } #endif @@ -414,12 +431,34 @@ heim_array_delete_value(heim_array_t array, size_t idx) heim_release(obj); } -#ifdef __BLOCKS__ /** - * Get value at idx + * Filter out entres of array when function return true * * @param array the array to modify - * @param idx the key to delete + * @param fn filter function + */ + +void +heim_array_filter_f(heim_array_t array, void *ctx, heim_array_filter_f_t fn) +{ + size_t n = 0; + + while (n < array->len) { + if (fn(array->val[n], ctx)) { + heim_array_delete_value(array, n); + } else { + n++; + } + } +} + +#ifdef __BLOCKS__ + +/** + * Filter out entres of array when block return true + * + * @param array the array to modify + * @param block filter block */ void diff --git a/lib/base/heimbase.h b/lib/base/heimbase.h index a99e10082..4cc0429e9 100644 --- a/lib/base/heimbase.h +++ b/lib/base/heimbase.h @@ -41,6 +41,8 @@ #include #include +#define HEIM_BASE_API_VERSION 20130210 + typedef void * heim_object_t; typedef unsigned int heim_tid_t; typedef heim_object_t heim_bool_t; @@ -121,15 +123,16 @@ typedef struct heim_array_data *heim_array_t; heim_array_t heim_array_create(void); heim_tid_t heim_array_get_type_id(void); -typedef void (*heim_array_iterator_f_t)(heim_object_t, void *); +typedef void (*heim_array_iterator_f_t)(heim_object_t, void *, int *); +typedef int (*heim_array_filter_f_t)(heim_object_t, void *); int heim_array_append_value(heim_array_t, heim_object_t); int heim_array_insert_value(heim_array_t, size_t idx, heim_object_t); void heim_array_iterate_f(heim_array_t, void *, heim_array_iterator_f_t); void heim_array_iterate_reverse_f(heim_array_t, void *, heim_array_iterator_f_t); #ifdef __BLOCKS__ -void heim_array_iterate(heim_array_t, void (^)(heim_object_t)); -void heim_array_iterate_reverse(heim_array_t, void (^)(heim_object_t)); +void heim_array_iterate(heim_array_t, void (^)(heim_object_t, int *)); +void heim_array_iterate_reverse(heim_array_t, void (^)(heim_object_t, int *)); #endif size_t heim_array_get_length(heim_array_t); heim_object_t @@ -138,6 +141,7 @@ heim_object_t heim_array_copy_value(heim_array_t, size_t); void heim_array_set_value(heim_array_t, size_t, heim_object_t); void heim_array_delete_value(heim_array_t, size_t); +void heim_array_filter_f(heim_array_t, void *, heim_array_filter_f_t); #ifdef __BLOCKS__ void heim_array_filter(heim_array_t, int (^)(heim_object_t)); #endif diff --git a/lib/base/json.c b/lib/base/json.c index 010ab333a..391b736b0 100644 --- a/lib/base/json.c +++ b/lib/base/json.c @@ -79,7 +79,7 @@ indent(struct twojson *j) } static void -array2json(heim_object_t value, void *ctx) +array2json(heim_object_t value, void *ctx, int *stop) { struct twojson *j = ctx; if (j->ret) diff --git a/lib/base/test_base.c b/lib/base/test_base.c index 838e6cfe2..d276f8fc7 100644 --- a/lib/base/test_base.c +++ b/lib/base/test_base.c @@ -795,7 +795,7 @@ struct test_array_iter_ctx { char buf[256]; }; -static void test_array_iter(heim_object_t elt, void *arg) +static void test_array_iter(heim_object_t elt, void *arg, int *stop) { struct test_array_iter_ctx *iter_ctx = arg; diff --git a/lib/gssapi/krb5/set_sec_context_option.c b/lib/gssapi/krb5/set_sec_context_option.c index 141ff722f..a0e6fd02c 100644 --- a/lib/gssapi/krb5/set_sec_context_option.c +++ b/lib/gssapi/krb5/set_sec_context_option.c @@ -178,23 +178,9 @@ _gsskrb5_set_sec_context_option } else if (gss_oid_equal(desired_object, GSS_KRB5_SEND_TO_KDC_X)) { - if (value == NULL || value->length == 0) { - krb5_set_send_to_kdc_func(context, NULL, NULL); - } else { - struct gsskrb5_send_to_kdc c; + *minor_status = EINVAL; + return GSS_S_FAILURE; - if (value->length != sizeof(c)) { - *minor_status = EINVAL; - return GSS_S_FAILURE; - } - memcpy(&c, value->value, sizeof(c)); - krb5_set_send_to_kdc_func(context, - (krb5_send_to_kdc_func)c.func, - c.ptr); - } - - *minor_status = 0; - return GSS_S_COMPLETE; } else if (gss_oid_equal(desired_object, GSS_KRB5_CCACHE_NAME_X)) { char *str; diff --git a/lib/krb5/context.c b/lib/krb5/context.c index 975db0bce..8fc1002af 100644 --- a/lib/krb5/context.c +++ b/lib/krb5/context.c @@ -100,7 +100,8 @@ init_context_from_config_file(krb5_context context) krb5_enctype *tmptypes; INIT_FIELD(context, time, max_skew, 5 * 60, "clockskew"); - INIT_FIELD(context, time, kdc_timeout, 3, "kdc_timeout"); + INIT_FIELD(context, time, kdc_timeout, 30, "kdc_timeout"); + INIT_FIELD(context, time, host_timeout, 3, "host_timeout"); INIT_FIELD(context, int, max_retries, 3, "max_retries"); INIT_FIELD(context, string, http_proxy, NULL, "http_proxy"); @@ -220,6 +221,7 @@ init_context_from_config_file(krb5_context context) INIT_FIELD(context, bool, srv_lookup, TRUE, "srv_lookup"); INIT_FIELD(context, bool, srv_lookup, context->srv_lookup, "dns_lookup_kdc"); INIT_FIELD(context, int, large_msg_size, 1400, "large_message_size"); + INIT_FIELD(context, int, max_msg_size, 1000 * 1024, "maximum_message_size"); INIT_FLAG(context, flags, KRB5_CTX_F_DNS_CANONICALIZE_HOSTNAME, TRUE, "dns_canonicalize_hostname"); INIT_FLAG(context, flags, KRB5_CTX_F_CHECK_PAC, TRUE, "check_pac"); context->default_cc_name = NULL; diff --git a/lib/krb5/deprecated.c b/lib/krb5/deprecated.c index 4aa60be8a..03c33531d 100644 --- a/lib/krb5/deprecated.c +++ b/lib/krb5/deprecated.c @@ -648,4 +648,97 @@ krb5_have_error_string(krb5_context context) return str != NULL; } +struct send_to_kdc { + krb5_send_to_kdc_func func; + void *data; +}; + +/* + * Send the data `send' to one host from `handle` and get back the reply + * in `receive'. + */ + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_sendto (krb5_context context, + const krb5_data *send_data, + krb5_krbhst_handle handle, + krb5_data *receive) +{ + krb5_error_code ret; + krb5_sendto_ctx ctx; + + ret = krb5_sendto_ctx_alloc(context, &ctx); + if (ret) + return ret; + _krb5_sendto_ctx_set_krb5hst(context, ctx, handle); + + ret = krb5_sendto_context(context, ctx, send_data, (char *)_krb5_krbhst_get_realm(handle), receive); + krb5_sendto_ctx_free(context, ctx); + return ret; +} + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_sendto_kdc(krb5_context context, + const krb5_data *send_data, + const krb5_realm *realm, + krb5_data *receive) +{ + return krb5_sendto_kdc_flags(context, send_data, realm, receive, 0); +} + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_sendto_kdc_flags(krb5_context context, + const krb5_data *send_data, + const krb5_realm *realm, + krb5_data *receive, + int flags) +{ + krb5_error_code ret; + krb5_sendto_ctx ctx; + + ret = krb5_sendto_ctx_alloc(context, &ctx); + if (ret) + return ret; + krb5_sendto_ctx_add_flags(ctx, flags); + krb5_sendto_ctx_set_func(ctx, _krb5_kdc_retry, NULL); + + ret = krb5_sendto_context(context, ctx, send_data, *realm, receive); + krb5_sendto_ctx_free(context, ctx); + return ret; +} + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_set_send_to_kdc_func(krb5_context context, + krb5_send_to_kdc_func func, + void *data) +{ + free(context->send_to_kdc); + if (func == NULL) { + context->send_to_kdc = NULL; + return 0; + } + + context->send_to_kdc = malloc(sizeof(*context->send_to_kdc)); + if (context->send_to_kdc == NULL) { + krb5_set_error_message(context, ENOMEM, + N_("malloc: out of memory", "")); + return ENOMEM; + } + + context->send_to_kdc->func = func; + context->send_to_kdc->data = data; + return 0; +} + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +_krb5_copy_send_to_kdc_func(krb5_context context, krb5_context to) +{ + if (context->send_to_kdc) + return krb5_set_send_to_kdc_func(to, + context->send_to_kdc->func, + context->send_to_kdc->data); + else + return krb5_set_send_to_kdc_func(to, NULL, NULL); +} + #endif /* HEIMDAL_SMALLER */ diff --git a/lib/krb5/heim_err.et b/lib/krb5/heim_err.et index c47f77092..da2c4c111 100644 --- a/lib/krb5/heim_err.et +++ b/lib/krb5/heim_err.et @@ -44,4 +44,9 @@ error_code NONAME, "nodename nor servname provided, or not known" error_code SERVICE, "servname not supported for ai_socktype" error_code SOCKTYPE, "ai_socktype not supported" error_code SYSTEM, "system error returned in errno" + +index 192 +prefix HEIM_NET +error_code CONN_REFUSED, "connection refused" + end diff --git a/lib/krb5/krb5.h b/lib/krb5/krb5.h index ed6dc1052..0bcd86e1e 100644 --- a/lib/krb5/krb5.h +++ b/lib/krb5/krb5.h @@ -689,6 +689,13 @@ typedef EncAPRepPart krb5_ap_rep_enc_part; #define KRB5_WELLKNOWN_ORG_H5L_REALM ("WELLKNOWN:ORG.H5L") #define KRB5_DIGEST_NAME ("digest") + +#define KRB5_PKU2U_REALM_NAME ("WELLKNOWN:PKU2U") +#define KRB5_LKDC_REALM_NAME ("WELLKNOWN:COM.APPLE.LKDC") + +#define KRB5_GSS_HOSTBASED_SERVICE_NAME ("WELLKNOWN:ORG.H5L.HOSTBASED-SERVICE") +#define KRB5_GSS_REFERALS_REALM_NAME ("WELLKNOWN:ORG.H5L.REFERALS-REALM") + typedef enum { KRB5_PROMPT_TYPE_PASSWORD = 0x1, KRB5_PROMPT_TYPE_NEW_PASSWORD = 0x2, @@ -822,6 +829,7 @@ enum { KRB5_KRBHST_FLAGS_LARGE_MSG = 2 }; +typedef krb5_error_code (*krb5_sendto_prexmit)(krb5_context, int, void *, int, krb5_data *); typedef krb5_error_code (KRB5_CALLCONV * krb5_send_to_kdc_func)(krb5_context, void *, krb5_krbhst_info *, time_t, const krb5_data *, krb5_data *); @@ -843,8 +851,13 @@ enum { typedef struct krb5_sendto_ctx_data *krb5_sendto_ctx; #define KRB5_SENDTO_DONE 0 -#define KRB5_SENDTO_RESTART 1 +#define KRB5_SENDTO_RESET 1 #define KRB5_SENDTO_CONTINUE 2 +#define KRB5_SENDTO_TIMEOUT 3 +#define KRB5_SENDTO_INITIAL 4 +#define KRB5_SENDTO_FILTER 5 +#define KRB5_SENDTO_FAILED 6 +#define KRB5_SENDTO_KRBHST 7 typedef krb5_error_code (KRB5_CALLCONV * krb5_sendto_ctx_func)(krb5_context, krb5_sendto_ctx, void *, diff --git a/lib/krb5/krb5_locl.h b/lib/krb5/krb5_locl.h index cff53acee..53499df3c 100644 --- a/lib/krb5/krb5_locl.h +++ b/lib/krb5/krb5_locl.h @@ -261,6 +261,7 @@ typedef struct krb5_context_data { char **default_realms; time_t max_skew; time_t kdc_timeout; + time_t host_timeout; unsigned max_retries; int32_t kdc_sec_offset; int32_t kdc_usec_offset; @@ -293,6 +294,8 @@ typedef struct krb5_context_data { int default_cc_name_set; void *mutex; /* protects error_string */ int large_msg_size; + int max_msg_size; + int tgs_negative_timeout; /* timeout for TGS negative cache */ int flags; #define KRB5_CTX_F_DNS_CANONICALIZE_HOSTNAME 1 #define KRB5_CTX_F_CHECK_PAC 2 @@ -303,6 +306,7 @@ typedef struct krb5_context_data { #ifdef PKINIT hx509_context hx509ctx; #endif + unsigned int num_kdc_requests; } krb5_context_data; #ifndef KRB5_USE_PATH_TOKENS @@ -340,6 +344,14 @@ typedef struct krb5_context_data { #define KRB5_FORWARDABLE_DEFAULT TRUE #endif +#ifndef KRB5_CONFIGURATION_CHANGE_NOTIFY_NAME +#define KRB5_CONFIGURATION_CHANGE_NOTIFY_NAME "org.h5l.Kerberos.configuration-changed" +#endif + +#ifndef KRB5_FALLBACK_DEFAULT +#define KRB5_FALLBACK_DEFAULT TRUE +#endif + #ifdef PKINIT struct krb5_pk_identity { diff --git a/lib/krb5/krbhst.c b/lib/krb5/krbhst.c index 8888890b4..be9411cf5 100644 --- a/lib/krb5/krbhst.c +++ b/lib/krb5/krbhst.c @@ -3,6 +3,8 @@ * (Royal Institute of Technology, Stockholm, Sweden). * All rights reserved. * + * Portions Copyright (c) 2010 Apple Inc. All rights reserved. + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: @@ -153,9 +155,11 @@ struct krb5_krbhst_data { #define KD_CONFIG_EXISTS 32 #define KD_LARGE_MSG 64 #define KD_PLUGIN 128 +#define KD_HOSTNAMES 256 krb5_error_code (*get_next)(krb5_context, struct krb5_krbhst_data *, krb5_krbhst_info**); + char *hostname; unsigned int fallback_count; struct krb5_krbhst_info *hosts, **index, **end; @@ -179,6 +183,12 @@ krbhst_get_default_proto(struct krb5_krbhst_data *kd) return KRB5_KRBHST_UDP; } +static int +krbhst_get_default_port(struct krb5_krbhst_data *kd) +{ + return kd->def_port; +} + /* * */ @@ -218,6 +228,7 @@ parse_hostspec(krb5_context context, struct krb5_krbhst_data *kd, hi->proto = KRB5_KRBHST_TCP; p += 4; } else if(strncmp(p, "udp/", 4) == 0) { + hi->proto = KRB5_KRBHST_UDP; p += 4; } @@ -438,6 +449,9 @@ srv_get_hosts(krb5_context context, struct krb5_krbhst_data *kd, krb5_krbhst_info **res; int count, i; + if (krb5_realm_is_lkdc(kd->realm)) + return; + ret = srv_find_realm(context, &res, &count, kd->realm, "SRV", proto, service, kd->port); _krb5_debug(context, 2, "searching DNS for realm %s %s.%s -> %d", @@ -492,14 +506,23 @@ fallback_get_hosts(krb5_context context, struct krb5_krbhst_data *kd, struct addrinfo hints; char portstr[NI_MAXSERV]; + ret = krb5_config_get_bool_default(context, NULL, KRB5_FALLBACK_DEFAULT, + "libdefaults", "use_fallback", NULL); + if (!ret) { + kd->flags |= KD_FALLBACK; + return 0; + } + _krb5_debug(context, 2, "fallback lookup %d for realm %s (service %s)", kd->fallback_count, kd->realm, serv_string); /* * Don't try forever in case the DNS server keep returning us * entries (like wildcard entries or the .nu TLD) + * + * Also don't try LKDC realms since fallback wont work on them at all. */ - if(kd->fallback_count >= 5) { + if(kd->fallback_count >= 5 || krb5_realm_is_lkdc(kd->realm)) { kd->flags |= KD_FALLBACK; return 0; } @@ -547,24 +570,18 @@ fallback_get_hosts(krb5_context context, struct krb5_krbhst_data *kd, */ static krb5_error_code -add_locate(void *ctx, int type, struct sockaddr *addr) +add_plugin_host(struct krb5_krbhst_data *kd, + const char *host, + const char *port, + int portnum, + int proto) { struct krb5_krbhst_info *hi; - struct krb5_krbhst_data *kd = ctx; - char host[NI_MAXHOST], port[NI_MAXSERV]; struct addrinfo hints, *ai; - socklen_t socklen; size_t hostlen; int ret; - socklen = socket_sockaddr_size(addr); - - ret = getnameinfo(addr, socklen, host, sizeof(host), port, sizeof(port), - NI_NUMERICHOST|NI_NUMERICSERV); - if (ret != 0) - return 0; - - make_hints(&hints, krbhst_get_default_proto(kd)); + make_hints(&hints, proto); ret = getaddrinfo(host, port, &hints, &ai); if (ret) return 0; @@ -575,8 +592,8 @@ add_locate(void *ctx, int type, struct sockaddr *addr) if(hi == NULL) return ENOMEM; - hi->proto = krbhst_get_default_proto(kd); - hi->port = hi->def_port = socket_get_port(addr); + hi->proto = proto; + hi->port = hi->def_port = portnum; hi->ai = ai; memmove(hi->hostname, host, hostlen); hi->hostname[hostlen] = '\0'; @@ -585,29 +602,71 @@ add_locate(void *ctx, int type, struct sockaddr *addr) return 0; } -struct ghcontext { - struct krb5_krbhst_data *kd; +static krb5_error_code +add_locate(void *ctx, int type, struct sockaddr *addr) +{ + struct krb5_krbhst_data *kd = ctx; + char host[NI_MAXHOST], port[NI_MAXSERV]; + socklen_t socklen; + krb5_error_code ret; + int proto, portnum; + + socklen = socket_sockaddr_size(addr); + portnum = socket_get_port(addr); + + ret = getnameinfo(addr, socklen, host, sizeof(host), port, sizeof(port), + NI_NUMERICHOST|NI_NUMERICSERV); + if (ret != 0) + return 0; + + if (kd->port) + snprintf(port, sizeof(port), "%d", kd->port); + else if (atoi(port) == 0) + snprintf(port, sizeof(port), "%d", krbhst_get_default_port(kd)); + + proto = krbhst_get_default_proto(kd); + + ret = add_plugin_host(kd, host, port, portnum, proto); + if (ret) + return ret; + + /* + * This is really kind of broken and should be solved a different + * way, some sites block UDP, and we don't, in the general case, + * fall back to TCP, that should also be done. But since that + * should require us to invert the whole "find kdc" stack, let put + * this in for now. + */ + + if (proto == KRB5_KRBHST_UDP) { + ret = add_plugin_host(kd, host, port, portnum, KRB5_KRBHST_TCP); + if (ret) + return ret; + } + + return 0; +} + +struct plctx { enum locate_service_type type; + struct krb5_krbhst_data *kd; + unsigned long flags; }; -static krb5_error_code KRB5_LIB_CALL -ghcallback(krb5_context context, const void *plug, void *plugctx, void *userctx) +static krb5_error_code +plcallback(krb5_context context, + const void *plug, void *plugctx, void *userctx) { - krb5plugin_service_locate_ftable *service; - struct ghcontext *uc = userctx; - krb5_error_code ret; - - service = (krb5plugin_service_locate_ftable *)plug; + const krb5plugin_service_locate_ftable *locate = plug; + struct plctx *plctx = userctx; - ret = service->lookup(plugctx, uc->type, uc->kd->realm, 0, 0, - add_locate, uc->kd); - if (ret && ret != KRB5_PLUGIN_NO_HANDLE) { - - } else if (ret) { - _krb5_debug(context, 2, "plugin found result for realm %s", uc->kd->realm); - uc->kd->flags |= KD_CONFIG_EXISTS; - } - return ret; + if (locate->minor_version >= KRB5_PLUGIN_LOCATE_VERSION_2) + return locate->lookup(plugctx, plctx->flags, plctx->type, plctx->kd->realm, 0, 0, add_locate, plctx->kd); + + if (plctx->flags & KRB5_PLF_ALLOW_HOMEDIR) + return locate->old_lookup(plugctx, plctx->type, plctx->kd->realm, 0, 0, add_locate, plctx->kd); + + return KRB5_PLUGIN_NO_HANDLE; } static void @@ -615,15 +674,31 @@ plugin_get_hosts(krb5_context context, struct krb5_krbhst_data *kd, enum locate_service_type type) { - struct ghcontext userctx; + struct plctx ctx = { type, kd, 0 }; - userctx.kd = kd; - userctx.type = type; + if (_krb5_homedir_access(context)) + ctx.flags |= KRB5_PLF_ALLOW_HOMEDIR; - (void)_krb5_plugin_run_f(context, "krb5", KRB5_PLUGIN_LOCATE, - 0, 0, &userctx, ghcallback); + _krb5_plugin_run_f(context, "krb5", KRB5_PLUGIN_LOCATE, + KRB5_PLUGIN_LOCATE_VERSION_0, + 0, &ctx, plcallback); } +/* + * + */ + +static void +hostnames_get_hosts(krb5_context context, + struct krb5_krbhst_data *kd, + const char *type) +{ + kd->flags |= KD_HOSTNAMES; + if (kd->hostname) + append_host_string(context, kd, kd->hostname, kd->def_port, kd->port); +} + + /* * */ @@ -635,6 +710,12 @@ kdc_get_next(krb5_context context, { krb5_error_code ret; + if ((kd->flags & KD_HOSTNAMES) == 0) { + hostnames_get_hosts(context, kd, "kdc"); + if(get_next(kd, host)) + return 0; + } + if ((kd->flags & KD_PLUGIN) == 0) { plugin_get_hosts(context, kd, locate_service_kdc); kd->flags |= KD_PLUGIN; @@ -807,60 +888,20 @@ kpasswd_get_next(krb5_context context, return KRB5_KDC_UNREACH; } -static krb5_error_code -krb524_get_next(krb5_context context, - struct krb5_krbhst_data *kd, - krb5_krbhst_info **host) +static void +krbhost_dealloc(void *ptr) { - if ((kd->flags & KD_PLUGIN) == 0) { - plugin_get_hosts(context, kd, locate_service_krb524); - kd->flags |= KD_PLUGIN; - if(get_next(kd, host)) - return 0; + struct krb5_krbhst_data *handle = (struct krb5_krbhst_data *)ptr; + krb5_krbhst_info *h, *next; + + for (h = handle->hosts; h != NULL; h = next) { + next = h->next; + _krb5_free_krbhst_info(h); } + if (handle->hostname) + free(handle->hostname); - if((kd->flags & KD_CONFIG) == 0) { - config_get_hosts(context, kd, "krb524_server"); - if(get_next(kd, host)) - return 0; - kd->flags |= KD_CONFIG; - } - - if (kd->flags & KD_CONFIG_EXISTS) { - _krb5_debug(context, 1, - "Configuration exists for realm %s, wont go to DNS", - kd->realm); - return KRB5_KDC_UNREACH; - } - - if(context->srv_lookup) { - if((kd->flags & KD_SRV_UDP) == 0) { - srv_get_hosts(context, kd, "udp", "krb524"); - kd->flags |= KD_SRV_UDP; - if(get_next(kd, host)) - return 0; - } - - if((kd->flags & KD_SRV_TCP) == 0) { - srv_get_hosts(context, kd, "tcp", "krb524"); - kd->flags |= KD_SRV_TCP; - if(get_next(kd, host)) - return 0; - } - } - - /* no matches -> try kdc */ - - if (krbhst_empty(kd)) { - kd->flags = 0; - kd->port = kd->def_port; - kd->get_next = kdc_get_next; - return (*kd->get_next)(context, kd, host); - } - - _krb5_debug(context, 0, "No kpasswd entries found for realm %s", kd->realm); - - return KRB5_KDC_UNREACH; + free(handle->realm); } static struct krb5_krbhst_data* @@ -871,11 +912,11 @@ common_init(krb5_context context, { struct krb5_krbhst_data *kd; - if((kd = calloc(1, sizeof(*kd))) == NULL) + if ((kd = heim_alloc(sizeof(*kd), "krbhst-context", krbhost_dealloc)) == NULL) return NULL; if((kd->realm = strdup(realm)) == NULL) { - free(kd); + heim_release(kd); return NULL; } @@ -918,6 +959,8 @@ krb5_krbhst_init_flags(krb5_context context, int def_port; const char *service; + *handle = NULL; + switch(type) { case KRB5_KRBHST_KDC: next = kdc_get_next; @@ -936,11 +979,6 @@ krb5_krbhst_init_flags(krb5_context context, KPASSWD_PORT)); service = "change_password"; break; - case KRB5_KRBHST_KRB524: - next = krb524_get_next; - def_port = ntohs(krb5_getportbyname (context, "krb524", "udp", 4444)); - service = "524"; - break; default: krb5_set_error_message(context, ENOTTY, N_("unknown krbhst type (%u)", ""), type); @@ -988,6 +1026,22 @@ krb5_krbhst_next_as_string(krb5_context context, return krb5_krbhst_format_string(context, host, hostname, hostlen); } +/* + * + */ + +krb5_error_code KRB5_LIB_FUNCTION +krb5_krbhst_set_hostname(krb5_context context, + krb5_krbhst_handle handle, + const char *hostname) +{ + if (handle->hostname) + free(handle->hostname); + handle->hostname = strdup(hostname); + if (handle->hostname == NULL) + return ENOMEM; + return 0; +} KRB5_LIB_FUNCTION void KRB5_LIB_CALL krb5_krbhst_reset(krb5_context context, krb5_krbhst_handle handle) @@ -998,20 +1052,11 @@ krb5_krbhst_reset(krb5_context context, krb5_krbhst_handle handle) KRB5_LIB_FUNCTION void KRB5_LIB_CALL krb5_krbhst_free(krb5_context context, krb5_krbhst_handle handle) { - krb5_krbhst_info *h, *next; - - if (handle == NULL) - return; - - for (h = handle->hosts; h != NULL; h = next) { - next = h->next; - _krb5_free_krbhst_info(h); - } - - free(handle->realm); - free(handle); + heim_release(handle); } +#ifndef HEIMDAL_SMALLER + /* backwards compatibility ahead */ static krb5_error_code @@ -1092,7 +1137,6 @@ krb5_get_krb524hst (krb5_context context, return gethostlist(context, *realm, KRB5_KRBHST_KRB524, hostlist); } - /* * return an malloced list of KDC's for `realm' in `hostlist' */ @@ -1120,3 +1164,5 @@ krb5_free_krbhst (krb5_context context, free (hostlist); return 0; } + +#endif /* HEIMDAL_SMALLER */ diff --git a/lib/krb5/locate_plugin.h b/lib/krb5/locate_plugin.h index b1b1f0ef2..5a9c7bcb7 100644 --- a/lib/krb5/locate_plugin.h +++ b/lib/krb5/locate_plugin.h @@ -3,6 +3,8 @@ * (Royal Institute of Technology, Stockholm, Sweden). * All rights reserved. * + * Portions Copyright (c) 2010 Apple Inc. All rights reserved. + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: @@ -37,6 +39,10 @@ #define HEIMDAL_KRB5_LOCATE_PLUGIN_H 1 #define KRB5_PLUGIN_LOCATE "service_locator" +#define KRB5_PLUGIN_LOCATE_VERSION 1 +#define KRB5_PLUGIN_LOCATE_VERSION_0 0 +#define KRB5_PLUGIN_LOCATE_VERSION_1 1 +#define KRB5_PLUGIN_LOCATE_VERSION_2 2 enum locate_service_type { locate_service_kdc = 1, @@ -47,7 +53,15 @@ enum locate_service_type { }; typedef krb5_error_code -(*krb5plugin_service_locate_lookup) (void *, enum locate_service_type, +(*krb5plugin_service_locate_lookup) (void *, unsigned long, enum locate_service_type, + const char *, int, int, + int (*)(void *,int,struct sockaddr *), + void *); + +#define KRB5_PLF_ALLOW_HOMEDIR 1 + +typedef krb5_error_code +(*krb5plugin_service_locate_lookup_old) (void *, enum locate_service_type, const char *, int, int, int (*)(void *,int,struct sockaddr *), void *); @@ -57,7 +71,8 @@ typedef struct krb5plugin_service_locate_ftable { int minor_version; krb5_error_code (*init)(krb5_context, void **); void (*fini)(void *); - krb5plugin_service_locate_lookup lookup; + krb5plugin_service_locate_lookup_old old_lookup; + krb5plugin_service_locate_lookup lookup; /* version 2 */ } krb5plugin_service_locate_ftable; #endif /* HEIMDAL_KRB5_LOCATE_PLUGIN_H */ diff --git a/lib/krb5/plugin.c b/lib/krb5/plugin.c index d916b8ff1..93c37a80a 100644 --- a/lib/krb5/plugin.c +++ b/lib/krb5/plugin.c @@ -589,7 +589,7 @@ search_modules(heim_object_t key, heim_object_t value, void *ctx) } static void -eval_results(heim_object_t value, void *ctx) +eval_results(heim_object_t value, void *ctx, int *stop) { struct plug *pl = value; struct iter_ctx *s = ctx; diff --git a/lib/krb5/principal.c b/lib/krb5/principal.c index b7c367d5c..f8cab3b02 100644 --- a/lib/krb5/principal.c +++ b/lib/krb5/principal.c @@ -1126,6 +1126,64 @@ krb5_parse_nametype(krb5_context context, const char *str, int32_t *nametype) return KRB5_PARSE_MALFORMED; } +/** + * Returns true if name is Kerberos NULL name + * + * @ingroup krb5_principal + */ + +krb5_boolean KRB5_LIB_FUNCTION +krb5_principal_is_null(krb5_context context, krb5_const_principal principal) +{ + if (principal->name.name_type == KRB5_NT_WELLKNOWN && + principal->name.name_string.len == 2 && + strcmp(principal->name.name_string.val[0], "WELLKNOWN") == 0 && + strcmp(principal->name.name_string.val[1], "NULL") == 0) + return TRUE; + return FALSE; +} + +const char _krb5_wellknown_lkdc[] = "WELLKNOWN:COM.APPLE.LKDC"; +static const char lkdc_prefix[] = "LKDC:"; + +/** + * Returns true if name is Kerberos an LKDC realm + * + * @ingroup krb5_principal + */ + +krb5_boolean KRB5_LIB_FUNCTION +krb5_realm_is_lkdc(const char *realm) +{ + + return strncmp(realm, lkdc_prefix, sizeof(lkdc_prefix)-1) == 0 || + strncmp(realm, _krb5_wellknown_lkdc, sizeof(_krb5_wellknown_lkdc) - 1) == 0; +} + +/** + * Returns true if name is Kerberos an LKDC realm + * + * @ingroup krb5_principal + */ + +krb5_boolean KRB5_LIB_FUNCTION +krb5_principal_is_lkdc(krb5_context context, krb5_const_principal principal) +{ + return krb5_realm_is_lkdc(principal->realm); +} + +/** + * Returns true if name is Kerberos an LKDC realm + * + * @ingroup krb5_principal + */ + +krb5_boolean KRB5_LIB_FUNCTION +krb5_principal_is_pku2u(krb5_context context, krb5_const_principal principal) +{ + return strcmp(principal->realm, KRB5_PKU2U_REALM_NAME) == 0; +} + /** * Check if the cname part of the principal is a krbtgt principal * diff --git a/lib/krb5/send_to_kdc.c b/lib/krb5/send_to_kdc.c index eb39807f1..8cb8d4e5c 100644 --- a/lib/krb5/send_to_kdc.c +++ b/lib/krb5/send_to_kdc.c @@ -3,6 +3,8 @@ * (Royal Institute of Technology, Stockholm, Sweden). * All rights reserved. * + * Portions Copyright (c) 2010 - 2013 Apple Inc. All rights reserved. + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: @@ -34,264 +36,23 @@ #include "krb5_locl.h" #include "send_to_kdc_plugin.h" -struct send_to_kdc { - krb5_send_to_kdc_func func; - void *data; -}; - -/* - * connect to a remote host and in the case of stream sockets, provide - * a timeout for the connexion. +/** + * @section send_to_kdc Locating and sending packets to the KDC + * + * The send to kdc code is responsible to request the list of KDC from + * the locate-kdc subsystem and then send requests to each of them. + * + * - Each second a new hostname is tried. + * - If the hostname have several addresses, the first will be tried + * directly then in turn the other will be tried every 3 seconds + * (host_timeout). + * - UDP requests are tried 3 times, and it tried with a individual timeout of kdc_timeout / 3. + * - TCP and HTTP requests are tried 1 time. + * + * Total wait time shorter then (number of addresses * 3) + kdc_timeout seconds. + * */ -static int -timed_connect(int s, struct addrinfo *addr, time_t tmout) -{ -#ifdef HAVE_POLL - socklen_t sl; - int so_err; - int flags; - int ret; - - if (addr->ai_socktype != SOCK_STREAM) - return connect(s, addr->ai_addr, addr->ai_addrlen); - - flags = fcntl(s, F_GETFL); - if (flags == -1) - return -1; - - fcntl(s, F_SETFL, flags | O_NONBLOCK); - ret = connect(s, addr->ai_addr, addr->ai_addrlen); - if (ret == -1 && errno != EINPROGRESS) - return -1; - - for (;;) { - struct pollfd fds; - - fds.fd = s; - fds.events = POLLIN | POLLOUT; - fds.revents = 0; - - ret = poll(&fds, 1, tmout * 1000); - if (ret != -1 || errno != EINTR) - break; - } - fcntl(s, F_SETFL, flags); - - if (ret != 1) - return -1; - - sl = sizeof(so_err); - ret = getsockopt(s, SOL_SOCKET, SO_ERROR, &so_err, &sl); - if (ret == -1) - return -1; - if (so_err != 0) - return -1; - - return 0; -#else - return connect(s, addr->ai_addr, addr->ai_addrlen); -#endif -} - -/* - * send the data in `req' on the socket `fd' (which is datagram iff udp) - * waiting `tmout' for a reply and returning the reply in `rep'. - * iff limit read up to this many bytes - * returns 0 and data in `rep' if succesful, otherwise -1 - */ - -static int -recv_loop (krb5_socket_t fd, - time_t tmout, - int udp, - size_t limit, - krb5_data *rep) -{ - fd_set fdset; - struct timeval timeout; - int ret; - int nbytes; - -#ifndef NO_LIMIT_FD_SETSIZE - if (fd >= FD_SETSIZE) { - return -1; - } -#endif - - krb5_data_zero(rep); - do { - FD_ZERO(&fdset); - FD_SET(fd, &fdset); - timeout.tv_sec = tmout; - timeout.tv_usec = 0; - ret = select (fd + 1, &fdset, NULL, NULL, &timeout); - if (ret < 0) { - if (errno == EINTR) - continue; - return -1; - } else if (ret == 0) { - return 0; - } else { - void *tmp; - - if (rk_SOCK_IOCTL (fd, FIONREAD, &nbytes) < 0) { - krb5_data_free (rep); - return -1; - } - if(nbytes <= 0) - return 0; - - if (limit) - nbytes = min((size_t)nbytes, limit - rep->length); - - tmp = realloc (rep->data, rep->length + nbytes); - if (tmp == NULL) { - krb5_data_free (rep); - return -1; - } - rep->data = tmp; - ret = recv (fd, (char*)tmp + rep->length, nbytes, 0); - if (ret < 0) { - krb5_data_free (rep); - return -1; - } - rep->length += ret; - } - } while(!udp && (limit == 0 || rep->length < limit)); - return 0; -} - -/* - * Send kerberos requests and receive a reply on a udp or any other kind - * of a datagram socket. See `recv_loop'. - */ - -static int -send_and_recv_udp(krb5_socket_t fd, - time_t tmout, - const krb5_data *req, - krb5_data *rep) -{ - if (send (fd, req->data, req->length, 0) < 0) - return -1; - - return recv_loop(fd, tmout, 1, 0, rep); -} - -/* - * `send_and_recv' for a TCP (or any other stream) socket. - * Since there are no record limits on a stream socket the protocol here - * is to prepend the request with 4 bytes of its length and the reply - * is similarly encoded. - */ - -static int -send_and_recv_tcp(krb5_socket_t fd, - time_t tmout, - const krb5_data *req, - krb5_data *rep) -{ - unsigned char len[4]; - unsigned long rep_len; - krb5_data len_data; - - _krb5_put_int(len, req->length, 4); - if(net_write (fd, len, sizeof(len)) < 0) - return -1; - if(net_write (fd, req->data, req->length) < 0) - return -1; - if (recv_loop (fd, tmout, 0, 4, &len_data) < 0) - return -1; - if (len_data.length != 4) { - krb5_data_free (&len_data); - return -1; - } - _krb5_get_int(len_data.data, &rep_len, 4); - krb5_data_free (&len_data); - if (recv_loop (fd, tmout, 0, rep_len, rep) < 0) - return -1; - if(rep->length != rep_len) { - krb5_data_free (rep); - return -1; - } - return 0; -} - -int -_krb5_send_and_recv_tcp(krb5_socket_t fd, - time_t tmout, - const krb5_data *req, - krb5_data *rep) -{ - return send_and_recv_tcp(fd, tmout, req, rep); -} - -/* - * `send_and_recv' tailored for the HTTP protocol. - */ - -static int -send_and_recv_http(krb5_socket_t fd, - time_t tmout, - const char *prefix, - const krb5_data *req, - krb5_data *rep) -{ - char *request = NULL; - char *str; - int ret; - int len = base64_encode(req->data, req->length, &str); - - if(len < 0) - return -1; - ret = asprintf(&request, "GET %s%s HTTP/1.0\r\n\r\n", prefix, str); - free(str); - if (ret < 0 || request == NULL) - return -1; - ret = net_write (fd, request, strlen(request)); - free (request); - if (ret < 0) - return ret; - ret = recv_loop(fd, tmout, 0, 0, rep); - if(ret) - return ret; - { - unsigned long rep_len; - char *s, *p; - - s = realloc(rep->data, rep->length + 1); - if (s == NULL) { - krb5_data_free (rep); - return -1; - } - s[rep->length] = 0; - p = strstr(s, "\r\n\r\n"); - if(p == NULL) { - krb5_data_zero(rep); - free(s); - return -1; - } - p += 4; - rep->data = s; - rep->length -= p - s; - if(rep->length < 4) { /* remove length */ - krb5_data_zero(rep); - free(s); - return -1; - } - rep->length -= 4; - _krb5_get_int(p, &rep_len, 4); - if (rep_len != rep->length) { - krb5_data_zero(rep); - free(s); - return -1; - } - memmove(rep->data, p + 4, rep->length); - } - return 0; -} - static int init_port(const char *s, int fallback) { @@ -304,293 +65,125 @@ init_port(const char *s, int fallback) return fallback; } -/* - * Return 0 if succesful, otherwise 1 - */ +struct send_via_plugin_s { + krb5_const_realm realm; + krb5_krbhst_info *hi; + time_t timeout; + const krb5_data *send_data; + krb5_data *receive; +}; + -static int -send_via_proxy (krb5_context context, - const krb5_krbhst_info *hi, - const krb5_data *send_data, - krb5_data *receive) +static krb5_error_code KRB5_LIB_CALL +kdccallback(krb5_context context, const void *plug, void *plugctx, void *userctx) { - char *proxy2 = strdup(context->http_proxy); - char *proxy = proxy2; - char *prefix = NULL; - char *colon; - struct addrinfo hints; - struct addrinfo *ai, *a; - int ret; - krb5_socket_t s = rk_INVALID_SOCKET; - char portstr[NI_MAXSERV]; - int tmp; + const krb5plugin_send_to_kdc_ftable *service = (const krb5plugin_send_to_kdc_ftable *)plug; + struct send_via_plugin_s *ctx = userctx; - if (proxy == NULL) - return ENOMEM; - if (strncmp (proxy, "http://", 7) == 0) - proxy += 7; + if (service->send_to_kdc == NULL) + return KRB5_PLUGIN_NO_HANDLE; + return service->send_to_kdc(context, plugctx, ctx->hi, ctx->timeout, + ctx->send_data, ctx->receive); +} - colon = strchr(proxy, ':'); - if(colon != NULL) - *colon++ = '\0'; - memset (&hints, 0, sizeof(hints)); - hints.ai_family = PF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - tmp = init_port (colon, htons(80)); - snprintf (portstr, sizeof(portstr), "%d", ntohs(tmp)); - ret = getaddrinfo (proxy, portstr, &hints, &ai); - free (proxy2); - if (ret) - return krb5_eai_to_heim_errno(ret, errno); +static krb5_error_code KRB5_LIB_CALL +realmcallback(krb5_context context, const void *plug, void *plugctx, void *userctx) +{ + const krb5plugin_send_to_kdc_ftable *service = (const krb5plugin_send_to_kdc_ftable *)plug; + struct send_via_plugin_s *ctx = userctx; - for (a = ai; a != NULL; a = a->ai_next) { - s = socket (a->ai_family, a->ai_socktype | SOCK_CLOEXEC, a->ai_protocol); - if (s < 0) - continue; - rk_cloexec(s); - if (timed_connect (s, a, context->kdc_timeout) < 0) { - rk_closesocket (s); - continue; - } - break; - } - if (a == NULL) { - freeaddrinfo (ai); - return 1; - } - freeaddrinfo (ai); - - ret = asprintf(&prefix, "http://%s/", hi->hostname); - if(ret < 0 || prefix == NULL) { - close(s); - return 1; - } - ret = send_and_recv_http(s, context->kdc_timeout, - prefix, send_data, receive); - rk_closesocket (s); - free(prefix); - if(ret == 0 && receive->length != 0) - return 0; - return 1; + if (service->send_to_realm == NULL) + return KRB5_PLUGIN_NO_HANDLE; + return service->send_to_realm(context, plugctx, ctx->realm, ctx->timeout, + ctx->send_data, ctx->receive); } static krb5_error_code -send_via_plugin(krb5_context context, - krb5_krbhst_info *hi, - time_t timeout, - const krb5_data *send_data, - krb5_data *receive) +kdc_via_plugin(krb5_context context, + krb5_krbhst_info *hi, + time_t timeout, + const krb5_data *send_data, + krb5_data *receive) { - struct krb5_plugin *list = NULL, *e; - krb5_error_code ret; + struct send_via_plugin_s userctx; - ret = _krb5_plugin_find(context, PLUGIN_TYPE_DATA, KRB5_PLUGIN_SEND_TO_KDC, &list); - if(ret != 0 || list == NULL) - return KRB5_PLUGIN_NO_HANDLE; + userctx.realm = NULL; + userctx.hi = hi; + userctx.timeout = timeout; + userctx.send_data = send_data; + userctx.receive = receive; - for (e = list; e != NULL; e = _krb5_plugin_get_next(e)) { - krb5plugin_send_to_kdc_ftable *service; - void *ctx; - - service = _krb5_plugin_get_symbol(e); - if (service->minor_version != 0) - continue; - - (*service->init)(context, &ctx); - ret = (*service->send_to_kdc)(context, ctx, hi, - timeout, send_data, receive); - (*service->fini)(ctx); - if (ret == 0) - break; - if (ret != KRB5_PLUGIN_NO_HANDLE) { - krb5_set_error_message(context, ret, - N_("Plugin send_to_kdc failed to " - "lookup with error: %d", ""), ret); - break; - } - } - _krb5_plugin_free(list); - return KRB5_PLUGIN_NO_HANDLE; + return _krb5_plugin_run_f(context, "krb5", KRB5_PLUGIN_SEND_TO_KDC, + KRB5_PLUGIN_SEND_TO_KDC_VERSION_0, 0, + &userctx, kdccallback); } - -/* - * Send the data `send' to one host from `handle` and get back the reply - * in `receive'. - */ - -KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL -krb5_sendto (krb5_context context, - const krb5_data *send_data, - krb5_krbhst_handle handle, - krb5_data *receive) +static krb5_error_code +realm_via_plugin(krb5_context context, + krb5_const_realm realm, + time_t timeout, + const krb5_data *send_data, + krb5_data *receive) { - krb5_error_code ret; - krb5_socket_t fd; - size_t i; + struct send_via_plugin_s userctx; - krb5_data_zero(receive); + userctx.realm = realm; + userctx.hi = NULL; + userctx.timeout = timeout; + userctx.send_data = send_data; + userctx.receive = receive; - for (i = 0; i < context->max_retries; ++i) { - krb5_krbhst_info *hi; - - while (krb5_krbhst_next(context, handle, &hi) == 0) { - struct addrinfo *ai, *a; - - _krb5_debug(context, 2, - "trying to communicate with host %s in realm %s", - hi->hostname, _krb5_krbhst_get_realm(handle)); - - if (context->send_to_kdc) { - struct send_to_kdc *s = context->send_to_kdc; - - ret = (*s->func)(context, s->data, hi, - context->kdc_timeout, send_data, receive); - if (ret == 0 && receive->length != 0) - goto out; - continue; - } - - ret = send_via_plugin(context, hi, context->kdc_timeout, - send_data, receive); - if (ret == 0 && receive->length != 0) - goto out; - else if (ret != KRB5_PLUGIN_NO_HANDLE) - continue; - - if(hi->proto == KRB5_KRBHST_HTTP && context->http_proxy) { - if (send_via_proxy (context, hi, send_data, receive) == 0) { - ret = 0; - goto out; - } - continue; - } - - ret = krb5_krbhst_get_addrinfo(context, hi, &ai); - if (ret) - continue; - - for (a = ai; a != NULL; a = a->ai_next) { - fd = socket (a->ai_family, a->ai_socktype | SOCK_CLOEXEC, a->ai_protocol); - if (rk_IS_BAD_SOCKET(fd)) - continue; - rk_cloexec(fd); - if (timed_connect (fd, a, context->kdc_timeout) < 0) { - rk_closesocket (fd); - continue; - } - switch (hi->proto) { - case KRB5_KRBHST_HTTP : - ret = send_and_recv_http(fd, context->kdc_timeout, - "", send_data, receive); - break; - case KRB5_KRBHST_TCP : - ret = send_and_recv_tcp (fd, context->kdc_timeout, - send_data, receive); - break; - case KRB5_KRBHST_UDP : - ret = send_and_recv_udp (fd, context->kdc_timeout, - send_data, receive); - break; - } - rk_closesocket (fd); - if(ret == 0 && receive->length != 0) - goto out; - } - } - krb5_krbhst_reset(context, handle); - } - krb5_clear_error_message (context); - ret = KRB5_KDC_UNREACH; -out: - _krb5_debug(context, 2, - "result of trying to talk to realm %s = %d", - _krb5_krbhst_get_realm(handle), ret); - return ret; + return _krb5_plugin_run_f(context, "krb5", KRB5_PLUGIN_SEND_TO_KDC, + KRB5_PLUGIN_SEND_TO_KDC_VERSION_2, 0, + &userctx, realmcallback); } -KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL -krb5_sendto_kdc(krb5_context context, - const krb5_data *send_data, - const krb5_realm *realm, - krb5_data *receive) -{ - return krb5_sendto_kdc_flags(context, send_data, realm, receive, 0); -} - -KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL -krb5_sendto_kdc_flags(krb5_context context, - const krb5_data *send_data, - const krb5_realm *realm, - krb5_data *receive, - int flags) -{ - krb5_error_code ret; - krb5_sendto_ctx ctx; - - ret = krb5_sendto_ctx_alloc(context, &ctx); - if (ret) - return ret; - krb5_sendto_ctx_add_flags(ctx, flags); - krb5_sendto_ctx_set_func(ctx, _krb5_kdc_retry, NULL); - - ret = krb5_sendto_context(context, ctx, send_data, *realm, receive); - krb5_sendto_ctx_free(context, ctx); - return ret; -} - -KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL -krb5_set_send_to_kdc_func(krb5_context context, - krb5_send_to_kdc_func func, - void *data) -{ - free(context->send_to_kdc); - if (func == NULL) { - context->send_to_kdc = NULL; - return 0; - } - - context->send_to_kdc = malloc(sizeof(*context->send_to_kdc)); - if (context->send_to_kdc == NULL) { - krb5_set_error_message(context, ENOMEM, - N_("malloc: out of memory", "")); - return ENOMEM; - } - - context->send_to_kdc->func = func; - context->send_to_kdc->data = data; - return 0; -} - -KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL -_krb5_copy_send_to_kdc_func(krb5_context context, krb5_context to) -{ - if (context->send_to_kdc) - return krb5_set_send_to_kdc_func(to, - context->send_to_kdc->func, - context->send_to_kdc->data); - else - return krb5_set_send_to_kdc_func(to, NULL, NULL); -} - - - struct krb5_sendto_ctx_data { int flags; int type; krb5_sendto_ctx_func func; void *data; + char *hostname; + krb5_krbhst_handle krbhst; + + /* context2 */ + const krb5_data *send_data; + krb5_data response; + heim_array_t hosts; + int stateflags; +#define KRBHST_COMPLETED 1 + + /* prexmit */ + krb5_sendto_prexmit prexmit_func; + void *prexmit_ctx; + + /* stats */ + struct { + struct timeval start_time; + struct timeval name_resolution; + struct timeval krbhst; + unsigned long sent_packets; + unsigned long num_hosts; + } stats; + unsigned int stid; }; +static void +dealloc_sendto_ctx(void *ptr) +{ + krb5_sendto_ctx ctx = (krb5_sendto_ctx)ptr; + if (ctx->hostname) + free(ctx->hostname); + heim_release(ctx->hosts); + heim_release(ctx->krbhst); +} + KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL krb5_sendto_ctx_alloc(krb5_context context, krb5_sendto_ctx *ctx) { - *ctx = calloc(1, sizeof(**ctx)); - if (*ctx == NULL) { - krb5_set_error_message(context, ENOMEM, - N_("malloc: out of memory", "")); - return ENOMEM; - } + *ctx = heim_alloc(sizeof(**ctx), "sendto-context", dealloc_sendto_ctx); + (*ctx)->hosts = heim_array_create(); + return 0; } @@ -612,7 +205,6 @@ krb5_sendto_ctx_set_type(krb5_sendto_ctx ctx, int type) ctx->type = type; } - KRB5_LIB_FUNCTION void KRB5_LIB_CALL krb5_sendto_ctx_set_func(krb5_sendto_ctx ctx, krb5_sendto_ctx_func func, @@ -623,88 +215,45 @@ krb5_sendto_ctx_set_func(krb5_sendto_ctx ctx, } KRB5_LIB_FUNCTION void KRB5_LIB_CALL -krb5_sendto_ctx_free(krb5_context context, krb5_sendto_ctx ctx) +_krb5_sendto_ctx_set_prexmit(krb5_sendto_ctx ctx, + krb5_sendto_prexmit prexmit, + void *data) { - memset(ctx, 0, sizeof(*ctx)); - free(ctx); + ctx->prexmit_func = prexmit; + ctx->prexmit_ctx = data; } KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL -krb5_sendto_context(krb5_context context, - krb5_sendto_ctx ctx, - const krb5_data *send_data, - const krb5_realm realm, - krb5_data *receive) +krb5_sendto_set_hostname(krb5_context context, + krb5_sendto_ctx ctx, + const char *hostname) { - krb5_error_code ret; - krb5_krbhst_handle handle = NULL; - int type, freectx = 0; - int action; - - krb5_data_zero(receive); - - if (ctx == NULL) { - freectx = 1; - ret = krb5_sendto_ctx_alloc(context, &ctx); - if (ret) - return ret; + if (ctx->hostname == NULL) + free(ctx->hostname); + ctx->hostname = strdup(hostname); + if (ctx->hostname == NULL) { + krb5_set_error_message(context, ENOMEM, N_("malloc: out of memory", "")); + return ENOMEM; } - - type = ctx->type; - if (type == 0) { - if ((ctx->flags & KRB5_KRBHST_FLAGS_MASTER) || context->use_admin_kdc) - type = KRB5_KRBHST_ADMIN; - else - type = KRB5_KRBHST_KDC; - } - - if ((int)send_data->length > context->large_msg_size) - ctx->flags |= KRB5_KRBHST_FLAGS_LARGE_MSG; - - /* loop until we get back a appropriate response */ - - do { - action = KRB5_SENDTO_DONE; - - krb5_data_free(receive); - - if (handle == NULL) { - ret = krb5_krbhst_init_flags(context, realm, type, - ctx->flags, &handle); - if (ret) { - if (freectx) - krb5_sendto_ctx_free(context, ctx); - return ret; - } - } - - ret = krb5_sendto(context, send_data, handle, receive); - if (ret) - break; - if (ctx->func) { - ret = (*ctx->func)(context, ctx, ctx->data, receive, &action); - if (ret) - break; - } - if (action != KRB5_SENDTO_CONTINUE) { - krb5_krbhst_free(context, handle); - handle = NULL; - } - } while (action != KRB5_SENDTO_DONE); - if (handle) - krb5_krbhst_free(context, handle); - if (ret == KRB5_KDC_UNREACH) - krb5_set_error_message(context, ret, - N_("unable to reach any KDC in realm %s", ""), - realm); - if (ret) - krb5_data_free(receive); - if (freectx) - krb5_sendto_ctx_free(context, ctx); - return ret; + return 0; } -krb5_error_code KRB5_CALLCONV +KRB5_LIB_FUNCTION void KRB5_LIB_CALL +_krb5_sendto_ctx_set_krb5hst(krb5_context context, + krb5_sendto_ctx ctx, + krb5_krbhst_handle handle) +{ + heim_release(ctx->krbhst); + ctx->krbhst = heim_retain(handle); +} + +KRB5_LIB_FUNCTION void KRB5_LIB_CALL +krb5_sendto_ctx_free(krb5_context context, krb5_sendto_ctx ctx) +{ + heim_release(ctx); +} + +krb5_error_code _krb5_kdc_retry(krb5_context context, krb5_sendto_ctx ctx, void *data, const krb5_data *reply, int *action) { @@ -722,7 +271,7 @@ _krb5_kdc_retry(krb5_context context, krb5_sendto_ctx ctx, void *data, if (krb5_sendto_ctx_get_flags(ctx) & KRB5_KRBHST_FLAGS_LARGE_MSG) break; krb5_sendto_ctx_add_flags(ctx, KRB5_KRBHST_FLAGS_LARGE_MSG); - *action = KRB5_SENDTO_RESTART; + *action = KRB5_SENDTO_RESET; break; } case KRB5KDC_ERR_SVC_UNAVAILABLE: @@ -731,3 +280,978 @@ _krb5_kdc_retry(krb5_context context, krb5_sendto_ctx ctx, void *data, } return 0; } + +/* + * + */ + +struct host; + +struct host_fun { + krb5_error_code (*prepare)(krb5_context, struct host *, const krb5_data *); + krb5_error_code (*send)(krb5_context, struct host *); + krb5_error_code (*recv)(krb5_context, struct host *, krb5_data *); + int ntries; +}; + +struct host { + enum host_state { CONNECT, CONNECTING, CONNECTED, WAITING_REPLY, DEAD } state; + krb5_krbhst_info *hi; + struct addrinfo *ai; + rk_socket_t fd; + struct host_fun *fun; + unsigned int tries; + time_t timeout; + krb5_data data; + unsigned int tid; +}; + +static void +debug_host(krb5_context context, int level, struct host *host, const char *fmt, ...) + __attribute__((__format__(__printf__, 4, 5))); + +static void +debug_host(krb5_context context, int level, struct host *host, const char *fmt, ...) +{ + const char *proto = "unknown"; + char name[NI_MAXHOST], port[NI_MAXSERV]; + char *text = NULL; + va_list ap; + int ret; + + if (!_krb5_have_debug(context, 5)) + return; + + va_start(ap, fmt); + ret = vasprintf(&text, fmt, ap); + va_end(ap); + if (ret == -1 || text == NULL) + return; + + if (host->hi->proto == KRB5_KRBHST_HTTP) + proto = "http"; + else if (host->hi->proto == KRB5_KRBHST_TCP) + proto = "tcp"; + else if (host->hi->proto == KRB5_KRBHST_UDP) + proto = "udp"; + + if (getnameinfo(host->ai->ai_addr, host->ai->ai_addrlen, + name, sizeof(name), port, sizeof(port), NI_NUMERICHOST) != 0) + name[0] = '\0'; + + _krb5_debug(context, level, "%s: %s %s:%s (%s) tid: %08x", text, + proto, name, port, host->hi->hostname, host->tid); + free(text); +} + + +static void +deallocate_host(void *ptr) +{ + struct host *host = ptr; + if (!rk_IS_BAD_SOCKET(host->fd)) + rk_closesocket(host->fd); + krb5_data_free(&host->data); + host->ai = NULL; +} + +static void +host_dead(krb5_context context, struct host *host, const char *msg) +{ + debug_host(context, 5, host, "%s", msg); + rk_closesocket(host->fd); + host->fd = rk_INVALID_SOCKET; + host->state = DEAD; +} + +static krb5_error_code +send_stream(krb5_context context, struct host *host) +{ + ssize_t len; + + len = write(host->fd, host->data.data, host->data.length); + + if (len < 0) + return errno; + else if (len < host->data.length) { + host->data.length -= len; + memmove(host->data.data, ((uint8_t *)host->data.data) + len, host->data.length - len); + return -1; + } else { + krb5_data_free(&host->data); + return 0; + } +} + +static krb5_error_code +recv_stream(krb5_context context, struct host *host) +{ + krb5_error_code ret; + size_t oldlen; + ssize_t sret; + int nbytes; + + if (rk_SOCK_IOCTL(host->fd, FIONREAD, &nbytes) != 0 || nbytes <= 0) + return HEIM_NET_CONN_REFUSED; + + if (context->max_msg_size - host->data.length < nbytes) { + krb5_set_error_message(context, KRB5KRB_ERR_FIELD_TOOLONG, + N_("TCP message from KDC too large %d", ""), + (int)(host->data.length + nbytes)); + return KRB5KRB_ERR_FIELD_TOOLONG; + } + + oldlen = host->data.length; + + ret = krb5_data_realloc(&host->data, oldlen + nbytes + 1 /* NUL */); + if (ret) + return ret; + + sret = read(host->fd, ((uint8_t *)host->data.data) + oldlen, nbytes); + if (sret <= 0) { + ret = errno; + return ret; + } + host->data.length = oldlen + sret; + /* zero terminate for http transport */ + ((uint8_t *)host->data.data)[host->data.length] = '\0'; + + return 0; +} + +/* + * + */ + +static void +host_next_timeout(krb5_context context, struct host *host) +{ + host->timeout = context->kdc_timeout / host->fun->ntries; + if (host->timeout == 0) + host->timeout = 1; + + host->timeout += time(NULL); +} + +/* + * connected host + */ + +static void +host_connected(krb5_context context, krb5_sendto_ctx ctx, struct host *host) +{ + krb5_error_code ret; + + host->state = CONNECTED; + /* + * Now prepare data to send to host + */ + if (ctx->prexmit_func) { + krb5_data data; + + krb5_data_zero(&data); + + ret = ctx->prexmit_func(context, host->hi->proto, + ctx->prexmit_ctx, host->fd, &data); + if (ret == 0) { + if (data.length == 0) { + host_dead(context, host, "prexmit function didn't send data"); + return; + } + ret = host->fun->prepare(context, host, &data); + krb5_data_free(&data); + } + + } else { + ret = host->fun->prepare(context, host, ctx->send_data); + } + if (ret) + debug_host(context, 5, host, "failed to prexmit/prepare"); +} + +/* + * connect host + */ + +static void +host_connect(krb5_context context, krb5_sendto_ctx ctx, struct host *host) +{ + krb5_krbhst_info *hi = host->hi; + struct addrinfo *ai = host->ai; + + debug_host(context, 5, host, "connecting to host"); + + if (connect(host->fd, ai->ai_addr, ai->ai_addrlen) < 0) { + if (errno == EINPROGRESS && (hi->proto == KRB5_KRBHST_HTTP || hi->proto == KRB5_KRBHST_TCP)) { + debug_host(context, 5, host, "connecting to %d", host->fd); + host->state = CONNECTING; + } else { + host_dead(context, host, "failed to connect"); + } + } else { + host_connected(context, ctx, host); + } + + host_next_timeout(context, host); +} + +/* + * HTTP transport + */ + +static krb5_error_code +prepare_http(krb5_context context, struct host *host, const krb5_data *data) +{ + char *str = NULL, *request = NULL; + krb5_error_code ret; + int len; + + heim_assert(host->data.length == 0, "prepare_http called twice"); + + len = base64_encode(data->data, data->length, &str); + if(len < 0) + return ENOMEM; + + if (context->http_proxy) + ret = asprintf(&request, "GET http://%s/%s HTTP/1.0\r\n\r\n", host->hi->hostname, str); + else + ret = asprintf(&request, "GET /%s HTTP/1.0\r\n\r\n", str); + free(str); + if(ret < 0 || request == NULL) + return ENOMEM; + + host->data.data = request; + host->data.length = strlen(request); + + return 0; +} + +static krb5_error_code +recv_http(krb5_context context, struct host *host, krb5_data *data) +{ + krb5_error_code ret; + unsigned long rep_len; + size_t len; + char *p; + + /* + * recv_stream returns a NUL terminated stream + */ + + ret = recv_stream(context, host); + if (ret) + return ret; + + p = strstr(host->data.data, "\r\n\r\n"); + if (p == NULL) + return -1; + p += 4; + + len = host->data.length - (p - (char *)host->data.data); + if (len < 4) + return KRB5KRB_ERR_FIELD_TOOLONG; + + _krb5_get_int(p, &rep_len, 4); + if (len < rep_len) + return -1; + + p += 4; + + memmove(host->data.data, p, rep_len); + host->data.length = rep_len; + + *data = host->data; + krb5_data_zero(&host->data); + + return 0; +} + +/* + * TCP transport + */ + +static krb5_error_code +prepare_tcp(krb5_context context, struct host *host, const krb5_data *data) +{ + krb5_error_code ret; + krb5_storage *sp; + + heim_assert(host->data.length == 0, "prepare_tcp called twice"); + + sp = krb5_storage_emem(); + if (sp == NULL) + return ENOMEM; + + ret = krb5_store_data(sp, *data); + if (ret) { + krb5_storage_free(sp); + return ret; + } + ret = krb5_storage_to_data(sp, &host->data); + krb5_storage_free(sp); + + return ret; +} + +static krb5_error_code +recv_tcp(krb5_context context, struct host *host, krb5_data *data) +{ + krb5_error_code ret; + unsigned long pktlen; + + ret = recv_stream(context, host); + if (ret) + return ret; + + if (host->data.length < 4) + return -1; + + _krb5_get_int(host->data.data, &pktlen, 4); + + if (pktlen > host->data.length - 4) + return -1; + + memmove(host->data.data, ((uint8_t *)host->data.data) + 4, host->data.length - 4); + host->data.length -= 4; + + *data = host->data; + krb5_data_zero(&host->data); + + return 0; +} + +/* + * UDP transport + */ + +static krb5_error_code +prepare_udp(krb5_context context, struct host *host, const krb5_data *data) +{ + return krb5_data_copy(&host->data, data->data, data->length); +} + +static krb5_error_code +send_udp(krb5_context context, struct host *host) +{ + if (send(host->fd, host->data.data, host->data.length, 0) < 0) + return errno; + return 0; +} + +static krb5_error_code +recv_udp(krb5_context context, struct host *host, krb5_data *data) +{ + krb5_error_code ret; + int nbytes; + + + if (rk_SOCK_IOCTL(host->fd, FIONREAD, &nbytes) != 0 || nbytes <= 0) + return HEIM_NET_CONN_REFUSED; + + if (context->max_msg_size < nbytes) { + krb5_set_error_message(context, KRB5KRB_ERR_FIELD_TOOLONG, + N_("UDP message from KDC too large %d", ""), + (int)nbytes); + return KRB5KRB_ERR_FIELD_TOOLONG; + } + + ret = krb5_data_alloc(data, nbytes); + if (ret) + return ret; + + ret = recv(host->fd, data->data, data->length, 0); + if (ret < 0) { + ret = errno; + krb5_data_free(data); + return ret; + } + data->length = ret; + + return 0; +} + +static struct host_fun http_fun = { + prepare_http, + send_stream, + recv_http, + 1 +}; +static struct host_fun tcp_fun = { + prepare_tcp, + send_stream, + recv_tcp, + 1 +}; +static struct host_fun udp_fun = { + prepare_udp, + send_udp, + recv_udp, + 3 +}; + + +/* + * Host state machine + */ + +static int +eval_host_state(krb5_context context, + krb5_sendto_ctx ctx, + struct host *host, + int readable, int writeable) +{ + krb5_error_code ret; + + if (host->state == CONNECT) { + /* check if its this host time to connect */ + if (host->timeout < time(NULL)) + host_connect(context, ctx, host); + return 0; + } + + if (host->state == CONNECTING && writeable) + host_connected(context, ctx, host); + + if (readable) { + + debug_host(context, 5, host, "reading packet"); + + ret = host->fun->recv(context, host, &ctx->response); + if (ret == -1) { + /* not done yet */ + } else if (ret == 0) { + /* if recv_foo function returns 0, we have a complete reply */ + debug_host(context, 5, host, "host completed"); + return 1; + } else { + host_dead(context, host, "host disconnected"); + } + } + + /* check if there is anything to send, state might DEAD after read */ + if (writeable && host->state == CONNECTED) { + + ctx->stats.sent_packets++; + + debug_host(context, 5, host, "writing packet"); + + ret = host->fun->send(context, host); + if (ret == -1) { + /* not done yet */ + } else if (ret) { + host_dead(context, host, "host dead, write failed"); + } else + host->state = WAITING_REPLY; + } + + return 0; +} + +/* + * + */ + +static krb5_error_code +submit_request(krb5_context context, krb5_sendto_ctx ctx, krb5_krbhst_info *hi) +{ + unsigned long submitted_host = 0; + krb5_boolean freeai = FALSE; + struct timeval nrstart, nrstop; + krb5_error_code ret; + struct addrinfo *ai = NULL, *a; + struct host *host; + + ret = kdc_via_plugin(context, hi, context->kdc_timeout, + ctx->send_data, &ctx->response); + if (ret == 0) { + return 0; + } else if (ret != KRB5_PLUGIN_NO_HANDLE) { + _krb5_debug(context, 5, "send via plugin failed %s: %d", + hi->hostname, ret); + return ret; + } + + /* + * If we have a proxy, let use the address of the proxy instead of + * the KDC and let the proxy deal with the resolving of the KDC. + */ + + gettimeofday(&nrstart, NULL); + + if (hi->proto == KRB5_KRBHST_HTTP && context->http_proxy) { + char *proxy2 = strdup(context->http_proxy); + char *el, *proxy = proxy2; + struct addrinfo hints; + char portstr[NI_MAXSERV]; + + if (proxy == NULL) + return ENOMEM; + if (strncmp(proxy, "http://", 7) == 0) + proxy += 7; + + /* check for url terminating slash */ + el = strchr(proxy, '/'); + if (el != NULL) + *el = '\0'; + + /* check for port in hostname, used below as port */ + el = strchr(proxy, ':'); + if(el != NULL) + *el++ = '\0'; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + snprintf(portstr, sizeof(portstr), "%d", + ntohs(init_port(el, htons(80)))); + + ret = getaddrinfo(proxy, portstr, &hints, &ai); + free(proxy2); + if (ret) + return krb5_eai_to_heim_errno(ret, errno); + + freeai = TRUE; + + } else { + ret = krb5_krbhst_get_addrinfo(context, hi, &ai); + if (ret) + return ret; + } + + /* add up times */ + gettimeofday(&nrstop, NULL); + timevalsub(&nrstop, &nrstart); + timevaladd(&ctx->stats.name_resolution, &nrstop); + + ctx->stats.num_hosts++; + + for (a = ai; a != NULL; a = a->ai_next) { + rk_socket_t fd; + + fd = socket(a->ai_family, a->ai_socktype | SOCK_CLOEXEC, a->ai_protocol); + if (rk_IS_BAD_SOCKET(fd)) + continue; + rk_cloexec(fd); + +#ifndef NO_LIMIT_FD_SETSIZE + if (fd >= FD_SETSIZE) { + _krb5_debug(context, 0, "fd too large for select"); + rk_closesocket(fd); + continue; + } +#endif + socket_set_nonblocking(fd, 1); + + host = heim_alloc(sizeof(*host), "sendto-host", deallocate_host); + if (host == NULL) { + rk_closesocket(fd); + return ENOMEM; + } + host->hi = hi; + host->fd = fd; + host->ai = a; + /* next version of stid */ + host->tid = ctx->stid = (ctx->stid & 0xffff0000) | ((ctx->stid & 0xffff) + 1); + + host->state = CONNECT; + + switch (host->hi->proto) { + case KRB5_KRBHST_HTTP : + host->fun = &http_fun; + break; + case KRB5_KRBHST_TCP : + host->fun = &tcp_fun; + break; + case KRB5_KRBHST_UDP : + host->fun = &udp_fun; + break; + default: + heim_abort("undefined http transport protocol: %d", (int)host->hi->proto); + } + + host->tries = host->fun->ntries; + + /* + * Connect directly next host, wait a host_timeout for each next address + */ + if (submitted_host == 0) + host_connect(context, ctx, host); + else { + debug_host(context, 5, host, + "Queuing host in future (in %ds), its the %lu address on the same name", + (int)(context->host_timeout * submitted_host), submitted_host + 1); + host->timeout = time(NULL) + (submitted_host * context->host_timeout); + } + + heim_array_append_value(ctx->hosts, host); + + heim_release(host); + + submitted_host++; + } + + if (freeai) + freeaddrinfo(ai); + + if (!submitted_host) + return KRB5_KDC_UNREACH; + + return 0; +} + +struct wait_ctx { + krb5_context context; + krb5_sendto_ctx ctx; + fd_set rfds; + fd_set wfds; + unsigned max_fd; + int got_reply; + time_t timenow; +}; + +static void +wait_setup(heim_object_t obj, void *iter_ctx, int *stop) +{ + struct wait_ctx *wait_ctx = iter_ctx; + struct host *h = (struct host *)obj; + + /* skip dead hosts */ + if (h->state == DEAD) + return; + + if (h->state == CONNECT) { + if (h->timeout < wait_ctx->timenow) + host_connect(wait_ctx->context, wait_ctx->ctx, h); + return; + } + + /* if host timed out, dec tries and (retry or kill host) */ + if (h->timeout < wait_ctx->timenow) { + heim_assert(h->tries != 0, "tries should not reach 0"); + h->tries--; + if (h->tries == 0) { + host_dead(wait_ctx->context, h, "host timed out"); + return; + } else { + debug_host(wait_ctx->context, 5, h, "retrying sending to"); + host_next_timeout(wait_ctx->context, h); + host_connected(wait_ctx->context, wait_ctx->ctx, h); + } + } + +#ifndef NO_LIMIT_FD_SETSIZE + heim_assert(h->fd < FD_SETSIZE, "fd too large"); +#endif + switch (h->state) { + case WAITING_REPLY: + FD_SET(h->fd, &wait_ctx->rfds); + break; + case CONNECTING: + case CONNECTED: + FD_SET(h->fd, &wait_ctx->rfds); + FD_SET(h->fd, &wait_ctx->wfds); + break; + default: + heim_abort("invalid sendto host state"); + } + if (h->fd > wait_ctx->max_fd) + wait_ctx->max_fd = h->fd; +} + +static int +wait_filter_dead(heim_object_t obj, void *ctx) +{ + struct host *h = (struct host *)obj; + return (int)((h->state == DEAD) ? true : false); +} + +static void +wait_process(heim_object_t obj, void *ctx, int *stop) +{ + struct wait_ctx *wait_ctx = ctx; + struct host *h = (struct host *)obj; + int readable, writeable; + heim_assert(h->state != DEAD, "dead host resurected"); + +#ifndef NO_LIMIT_FD_SETSIZE + heim_assert(h->fd < FD_SETSIZE, "fd too large"); +#endif + readable = FD_ISSET(h->fd, &wait_ctx->rfds); + writeable = FD_ISSET(h->fd, &wait_ctx->wfds); + + if (readable || writeable || h->state == CONNECT) + wait_ctx->got_reply |= eval_host_state(wait_ctx->context, wait_ctx->ctx, h, readable, writeable); + + /* if there is already a reply, just fall though the array */ + if (wait_ctx->got_reply) + *stop = 1; +} + +static krb5_error_code +wait_response(krb5_context context, int *action, krb5_sendto_ctx ctx) +{ + struct wait_ctx wait_ctx; + struct timeval tv; + int ret; + + wait_ctx.context = context; + wait_ctx.ctx = ctx; + FD_ZERO(&wait_ctx.rfds); + FD_ZERO(&wait_ctx.wfds); + wait_ctx.max_fd = 0; + + /* oh, we have a reply, it must be a plugin that got it for us */ + if (ctx->response.length) { + *action = KRB5_SENDTO_FILTER; + return 0; + } + + wait_ctx.timenow = time(NULL); + + heim_array_iterate_f(ctx->hosts, &wait_ctx, wait_setup); + heim_array_filter_f(ctx->hosts, &wait_ctx, wait_filter_dead); + + if (heim_array_get_length(ctx->hosts) == 0) { + if (ctx->stateflags & KRBHST_COMPLETED) { + _krb5_debug(context, 5, "no more hosts to send/recv packets to/from " + "trying to pulling more hosts"); + *action = KRB5_SENDTO_FAILED; + } else { + _krb5_debug(context, 5, "no more hosts to send/recv packets to/from " + "and no more hosts -> failure"); + *action = KRB5_SENDTO_TIMEOUT; + } + return 0; + } + + tv.tv_sec = 1; + tv.tv_usec = 0; + + ret = select(wait_ctx.max_fd + 1, &wait_ctx.rfds, &wait_ctx.wfds, NULL, &tv); + if (ret < 0) + return errno; + if (ret == 0) { + *action = KRB5_SENDTO_TIMEOUT; + return 0; + } + + wait_ctx.got_reply = 0; + heim_array_iterate_f(ctx->hosts, &wait_ctx, wait_process); + if (wait_ctx.got_reply) + *action = KRB5_SENDTO_FILTER; + else + *action = KRB5_SENDTO_CONTINUE; + + return 0; +} + +static void +reset_context(krb5_context context, krb5_sendto_ctx ctx) +{ + krb5_data_free(&ctx->response); + heim_release(ctx->hosts); + ctx->hosts = heim_array_create(); + ctx->stateflags = 0; +} + + +/* + * + */ + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_sendto_context(krb5_context context, + krb5_sendto_ctx ctx, + const krb5_data *send_data, + krb5_const_realm realm, + krb5_data *receive) +{ + krb5_error_code ret; + krb5_krbhst_handle handle = NULL; + struct timeval nrstart, nrstop, stop_time; + int type, freectx = 0; + int action; + int numreset = 0; + + krb5_data_zero(receive); + + if (ctx == NULL) { + ret = krb5_sendto_ctx_alloc(context, &ctx); + if (ret) + goto out; + freectx = 1; + } + + ctx->stid = (context->num_kdc_requests++) << 16; + + memset(&ctx->stats, 0, sizeof(ctx->stats)); + gettimeofday(&ctx->stats.start_time, NULL); + + type = ctx->type; + if (type == 0) { + if ((ctx->flags & KRB5_KRBHST_FLAGS_MASTER) || context->use_admin_kdc) + type = KRB5_KRBHST_ADMIN; + else + type = KRB5_KRBHST_KDC; + } + + ctx->send_data = send_data; + + if ((int)send_data->length > context->large_msg_size) + ctx->flags |= KRB5_KRBHST_FLAGS_LARGE_MSG; + + /* loop until we get back a appropriate response */ + + action = KRB5_SENDTO_INITIAL; + + while (action != KRB5_SENDTO_DONE && action != KRB5_SENDTO_FAILED) { + krb5_krbhst_info *hi; + + switch (action) { + case KRB5_SENDTO_INITIAL: + ret = realm_via_plugin(context, realm, context->kdc_timeout, + send_data, receive); + if (ret == 0 || ret != KRB5_PLUGIN_NO_HANDLE) { + action = KRB5_SENDTO_DONE; + break; + } + action = KRB5_SENDTO_KRBHST; + /* FALLTHOUGH */ + case KRB5_SENDTO_KRBHST: + if (ctx->krbhst == NULL) { + ret = krb5_krbhst_init_flags(context, realm, type, + ctx->flags, &handle); + if (ret) + goto out; + + if (ctx->hostname) { + ret = krb5_krbhst_set_hostname(context, handle, ctx->hostname); + if (ret) + goto out; + } + + } else { + handle = heim_retain(ctx->krbhst); + } + action = KRB5_SENDTO_TIMEOUT; + /* FALLTHOUGH */ + case KRB5_SENDTO_TIMEOUT: + + /* + * If we completed, just got to next step + */ + + if (ctx->stateflags & KRBHST_COMPLETED) { + action = KRB5_SENDTO_CONTINUE; + break; + } + + /* + * Pull out next host, if there is no more, close the + * handle and mark as completed. + * + * Collect time spent in krbhst (dns, plugin, etc) + */ + + + gettimeofday(&nrstart, NULL); + + ret = krb5_krbhst_next(context, handle, &hi); + + gettimeofday(&nrstop, NULL); + timevalsub(&nrstop, &nrstart); + timevaladd(&ctx->stats.krbhst, &nrstop); + + action = KRB5_SENDTO_CONTINUE; + if (ret == 0) { + _krb5_debug(context, 5, "submissing new requests to new host"); + if (submit_request(context, ctx, hi) != 0) + action = KRB5_SENDTO_TIMEOUT; + } else { + _krb5_debug(context, 5, "out of hosts, waiting for replies"); + ctx->stateflags |= KRBHST_COMPLETED; + } + + break; + case KRB5_SENDTO_CONTINUE: + + ret = wait_response(context, &action, ctx); + if (ret) + goto out; + + break; + case KRB5_SENDTO_RESET: + /* start over */ + _krb5_debug(context, 5, + "krb5_sendto trying over again (reset): %d", + numreset); + reset_context(context, ctx); + if (handle) { + krb5_krbhst_free(context, handle); + handle = NULL; + } + numreset++; + if (numreset >= 3) + action = KRB5_SENDTO_FAILED; + else + action = KRB5_SENDTO_KRBHST; + + break; + case KRB5_SENDTO_FILTER: + /* default to next state, the filter function might modify this */ + action = KRB5_SENDTO_DONE; + + if (ctx->func) { + ret = (*ctx->func)(context, ctx, ctx->data, + &ctx->response, &action); + if (ret) + goto out; + } + break; + case KRB5_SENDTO_FAILED: + ret = KRB5_KDC_UNREACH; + break; + case KRB5_SENDTO_DONE: + ret = 0; + break; + default: + heim_abort("invalid krb5_sendto_context state"); + } + } + + gettimeofday(&stop_time, NULL); + timevalsub(&stop_time, &ctx->stats.start_time); + + out: + if (ret == 0 && ctx->response.length) { + *receive = ctx->response; + krb5_data_zero(&ctx->response); + } else { + krb5_data_free(&ctx->response); + krb5_clear_error_message (context); + ret = KRB5_KDC_UNREACH; + krb5_set_error_message(context, ret, + N_("unable to reach any KDC in realm %s", ""), + realm); + } + + _krb5_debug(context, 1, + "krb5_sendto_context %s done: %d hosts %lu packets %lu wc: %ld.%06d nr: %ld.%06d kh: %ld.%06d tid: %08x", + realm, ret, + ctx->stats.num_hosts, ctx->stats.sent_packets, + stop_time.tv_sec, stop_time.tv_usec, + ctx->stats.name_resolution.tv_sec, ctx->stats.name_resolution.tv_usec, + ctx->stats.krbhst.tv_sec, ctx->stats.krbhst.tv_usec, ctx->stid); + + + if (freectx) + krb5_sendto_ctx_free(context, ctx); + else + reset_context(context, ctx); + + if (handle) + krb5_krbhst_free(context, handle); + + return ret; +} diff --git a/lib/krb5/send_to_kdc_plugin.h b/lib/krb5/send_to_kdc_plugin.h index c729a1286..11712b274 100644 --- a/lib/krb5/send_to_kdc_plugin.h +++ b/lib/krb5/send_to_kdc_plugin.h @@ -40,6 +40,10 @@ #define KRB5_PLUGIN_SEND_TO_KDC "send_to_kdc" +#define KRB5_PLUGIN_SEND_TO_KDC_VERSION_0 0 +#define KRB5_PLUGIN_SEND_TO_KDC_VERSION_2 2 +#define KRB5_PLUGIN_SEND_TO_KDC_VERSION KRB5_PLUGIN_SEND_TO_KDC_VERSION_2 + typedef krb5_error_code (*krb5plugin_send_to_kdc_func)(krb5_context, void *, @@ -47,12 +51,21 @@ typedef krb5_error_code time_t timeout, const krb5_data *, krb5_data *); +typedef krb5_error_code +(*krb5plugin_send_to_realm_func)(krb5_context, + void *, + krb5_const_realm, + time_t timeout, + const krb5_data *, + krb5_data *); + typedef struct krb5plugin_send_to_kdc_ftable { int minor_version; krb5_error_code (*init)(krb5_context, void **); void (*fini)(void *); krb5plugin_send_to_kdc_func send_to_kdc; + krb5plugin_send_to_realm_func send_to_realm; /* added in version 2 */ } krb5plugin_send_to_kdc_ftable; #endif /* HEIMDAL_KRB5_SEND_TO_KDC_PLUGIN_H */