tests: Use here-doc kadmin in Java test
[heimdal.git] / kdc / altsecid_gss_preauth_authorizer.c
blob17d3ee31bfdb297060c1408727fdfb4805d33c36
1 /*
2 * Copyright (c) 2021, PADL Software Pty Ltd.
3 * All rights reserved.
5 * Portions Copyright (c) 2004 Kungliga Tekniska Högskolan
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
18 * 3. Neither the name of PADL Software nor the names of its contributors
19 * may be used to endorse or promote products derived from this software
20 * without specific prior written permission.
22 * THIS SOFTWARE IS PROVIDED BY PADL SOFTWARE AND CONTRIBUTORS ``AS IS'' AND
23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 * ARE DISCLAIMED. IN NO EVENT SHALL PADL SOFTWARE OR CONTRIBUTORS BE LIABLE
26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 * SUCH DAMAGE.
36 * This plugin authorizes federated GSS-API pre-authentication clients by
37 * querying an AD DC in the KDC realm for the altSecurityIdentities
38 * attribute.
40 * For example, GSS-API initiator foo@AAA.H5L.SE using the eap-aes128
41 * mechanism to authenticate in realm H5L.SE would require a user entry
42 * where altSecurityIdentities equals either:
44 * EAP:foo@AAA.H5L.SE
45 * EAP-AES128:foo@AAA.H5L.SE
47 * (Stripping the mechanism name after the hyphen is a convention
48 * intended to allow mechanism variants to be grouped together.)
50 * Once the user entry is found, the name is canonicalized by reading the
51 * sAMAccountName attribute and concatenating it with the KDC realm,
52 * specifically the canonicalized realm of the WELLKNOWN/FEDERATED HDB
53 * entry.
55 * The KDC will need to have access to a default credentials cache, or
56 * OpenLDAP will need to be linked against a version of Cyrus SASL and
57 * Heimdal that supports keytab credentials.
60 #include "kdc_locl.h"
62 #include <resolve.h>
63 #include <common_plugin.h>
64 #include <heimqueue.h>
66 #include <gssapi/gssapi.h>
67 #include <gssapi_mech.h>
69 #include <ldap.h>
71 #include "gss_preauth_authorizer_plugin.h"
73 #ifndef PAC_REQUESTOR_SID
74 #define PAC_REQUESTOR_SID 18
75 #endif
77 struct ad_server_tuple {
78 HEIM_TAILQ_ENTRY(ad_server_tuple) link;
79 char *realm;
80 LDAP *ld;
81 #ifdef LDAP_OPT_X_SASL_GSS_CREDS
82 gss_cred_id_t gss_cred;
83 #endif
86 struct altsecid_gss_preauth_authorizer_context {
87 HEIM_TAILQ_HEAD(ad_server_tuple_list, ad_server_tuple) servers;
90 static int
91 sasl_interact(LDAP *ld, unsigned flags, void *defaults, void *interact)
93 return LDAP_SUCCESS;
96 #ifdef LDAP_OPT_X_SASL_GSS_CREDS
97 static krb5_error_code
98 ad_acquire_cred(krb5_context context,
99 krb5_const_realm realm,
100 struct ad_server_tuple *server)
102 const char *keytab_name = NULL;
103 char *keytab_name_buf = NULL;
104 krb5_error_code ret;
106 OM_uint32 major, minor;
107 gss_key_value_element_desc client_keytab;
108 gss_key_value_set_desc cred_store;
109 gss_OID_set_desc desired_mechs;
111 desired_mechs.count = 1;
112 desired_mechs.elements = GSS_KRB5_MECHANISM;
114 keytab_name = krb5_config_get_string(context, NULL, "kdc", realm,
115 "gss_altsecid_authorizer_keytab_name", NULL);
116 if (keytab_name == NULL)
117 keytab_name = krb5_config_get_string(context, NULL, "kdc",
118 "gss_altsecid_authorizer_keytab_name", NULL);
119 if (keytab_name == NULL) {
120 ret = _krb5_kt_client_default_name(context, &keytab_name_buf);
121 if (ret)
122 return ret;
124 keytab_name = keytab_name_buf;
127 client_keytab.key = "client_keytab";
128 client_keytab.value = keytab_name;
130 cred_store.count = 1;
131 cred_store.elements = &client_keytab;
133 major = gss_acquire_cred_from(&minor, GSS_C_NO_NAME, GSS_C_INDEFINITE,
134 &desired_mechs, GSS_C_INITIATE,
135 &cred_store, &server->gss_cred, NULL, NULL);
136 if (GSS_ERROR(major))
137 ret = minor ? minor : KRB5_KT_NOTFOUND;
138 else
139 ret = 0;
141 krb5_xfree(keytab_name_buf);
143 return ret;
145 #endif
147 static krb5_boolean
148 is_recoverable_ldap_err_p(int lret)
150 return
151 (lret == LDAP_SERVER_DOWN ||
152 lret == LDAP_TIMEOUT ||
153 lret == LDAP_UNAVAILABLE ||
154 lret == LDAP_BUSY ||
155 lret == LDAP_CONNECT_ERROR);
158 static krb5_error_code
159 ad_connect(krb5_context context,
160 krb5_const_realm realm,
161 struct ad_server_tuple *server)
163 krb5_error_code ret;
164 struct {
165 char *server;
166 int port;
167 } *s, *servers = NULL;
168 size_t i, num_servers = 0;
170 if (krb5_config_get_bool(context, NULL, "libdefaults", "block_dns",
171 NULL)) {
172 ret = KRB5KDC_ERR_SVC_UNAVAILABLE;
173 krb5_set_error_message(context, ret, "DNS blocked when finding AD DC");
174 return ret;
178 struct rk_dns_reply *r;
179 struct rk_resource_record *rr;
180 char *domain;
182 asprintf(&domain, "_ldap._tcp.%s", realm);
183 if (domain == NULL) {
184 ret = krb5_enomem(context);
185 goto out;
188 r = rk_dns_lookup(domain, "SRV");
189 free(domain);
190 if (r == NULL) {
191 krb5_set_error_message(context, KRB5KDC_ERR_SVC_UNAVAILABLE,
192 "Couldn't find AD DC in DNS");
193 ret = KRB5KDC_ERR_SVC_UNAVAILABLE;
194 goto out;
197 for (rr = r->head ; rr != NULL; rr = rr->next) {
198 if (rr->type != rk_ns_t_srv)
199 continue;
200 s = realloc(servers, sizeof(*servers) * (num_servers + 1));
201 if (s == NULL) {
202 ret = krb5_enomem(context);
203 rk_dns_free_data(r);
204 goto out;
206 servers = s;
207 num_servers++;
208 servers[num_servers - 1].port = rr->u.srv->port;
209 servers[num_servers - 1].server = strdup(rr->u.srv->target);
211 rk_dns_free_data(r);
214 #ifdef LDAP_OPT_X_SASL_GSS_CREDS
215 if (server->gss_cred == GSS_C_NO_CREDENTIAL) {
216 ret = ad_acquire_cred(context, realm, server);
217 if (ret)
218 goto out;
220 #endif
222 for (i = 0; i < num_servers; i++) {
223 int lret, version = LDAP_VERSION3;
224 LDAP *ld;
225 char *url = NULL;
227 asprintf(&url, "ldap://%s:%d", servers[i].server, servers[i].port);
228 if (url == NULL) {
229 ret = krb5_enomem(context);
230 goto out;
233 lret = ldap_initialize(&ld, url);
234 free(url);
235 if (lret != LDAP_SUCCESS)
236 continue;
238 ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version);
239 ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF);
240 #ifdef LDAP_OPT_X_SASL_GSS_CREDS
241 ldap_set_option(ld, LDAP_OPT_X_SASL_GSS_CREDS, server->gss_cred);
242 #endif
244 lret = ldap_sasl_interactive_bind_s(ld, NULL, "GSS-SPNEGO",
245 NULL, NULL, LDAP_SASL_QUIET,
246 sasl_interact, NULL);
247 if (lret != LDAP_SUCCESS) {
248 krb5_set_error_message(context, 0,
249 "Couldn't bind to AD DC %s:%d: %s",
250 servers[i].server, servers[i].port,
251 ldap_err2string(lret));
252 ldap_unbind_ext_s(ld, NULL, NULL);
253 continue;
256 server->ld = ld;
257 break;
260 ret = (server->ld != NULL) ? 0 : KRB5_KDC_UNREACH;
262 out:
263 for (i = 0; i < num_servers; i++)
264 free(servers[i].server);
265 free(servers);
267 if (ret && server->ld) {
268 ldap_unbind_ext_s(server->ld, NULL, NULL);
269 server->ld = NULL;
272 return ret;
275 static krb5_error_code
276 ad_lookup(krb5_context context,
277 krb5_const_realm realm,
278 struct ad_server_tuple *server,
279 gss_const_name_t initiator_name,
280 gss_const_OID mech_type,
281 krb5_principal *canon_principal,
282 kdc_data_t *requestor_sid)
284 krb5_error_code ret;
285 OM_uint32 minor;
286 const char *mech_type_str, *p;
287 char *filter = NULL;
288 gss_buffer_desc initiator_name_buf = GSS_C_EMPTY_BUFFER;
289 LDAPMessage *m = NULL, *m0;
290 char *basedn = NULL;
291 int lret;
292 char *attrs[] = { "sAMAccountName", "objectSid", NULL };
293 struct berval **values = NULL;
295 *canon_principal = NULL;
296 if (requestor_sid)
297 *requestor_sid = NULL;
299 mech_type_str = gss_oid_to_name(mech_type);
300 if (mech_type_str == NULL) {
301 ret = KRB5_PREAUTH_BAD_TYPE; /* should never happen */
302 goto out;
305 ret = KRB5_KDC_ERR_CLIENT_NOT_TRUSTED;
307 if (GSS_ERROR(gss_display_name(&minor, initiator_name,
308 &initiator_name_buf, NULL)))
309 goto out;
311 if ((p = strrchr(mech_type_str, '-')) != NULL) {
312 asprintf(&filter, "(&(objectClass=user)"
313 "(|(altSecurityIdentities=%.*s:%.*s)(altSecurityIdentities=%s:%.*s)))",
314 (int)(p - mech_type_str), mech_type_str,
315 (int)initiator_name_buf.length, (char *)initiator_name_buf.value,
316 mech_type_str,
317 (int)initiator_name_buf.length,
318 (char *)initiator_name_buf.value);
319 } else {
320 asprintf(&filter, "(&(objectClass=user)(altSecurityIdentities=%s:%.*s))",
321 mech_type_str,
322 (int)initiator_name_buf.length,
323 (char *)initiator_name_buf.value);
325 if (filter == NULL)
326 goto enomem;
328 lret = ldap_domain2dn(realm, &basedn);
329 if (lret != LDAP_SUCCESS)
330 goto out;
332 lret = ldap_search_ext_s(server->ld, basedn, LDAP_SCOPE_SUBTREE,
333 filter, attrs, 0,
334 NULL, NULL, NULL, 1, &m);
335 if (lret == LDAP_SIZELIMIT_EXCEEDED)
336 ret = KRB5KDC_ERR_PRINCIPAL_NOT_UNIQUE;
337 else if (is_recoverable_ldap_err_p(lret))
338 ret = KRB5KDC_ERR_SVC_UNAVAILABLE;
339 if (lret != LDAP_SUCCESS)
340 goto out;
342 m0 = ldap_first_entry(server->ld, m);
343 if (m0 == NULL)
344 goto out;
346 values = ldap_get_values_len(server->ld, m0, "sAMAccountName");
347 if (values == NULL ||
348 ldap_count_values_len(values) == 0)
349 goto out;
351 ret = krb5_make_principal(context, canon_principal, realm,
352 values[0]->bv_val, NULL);
353 if (ret)
354 goto out;
356 if (requestor_sid) {
357 ldap_value_free_len(values);
359 values = ldap_get_values_len(server->ld, m0, "objectSid");
360 if (values == NULL ||
361 ldap_count_values_len(values) == 0)
362 goto out;
364 *requestor_sid = kdc_data_create(values[0]->bv_val, values[0]->bv_len);
365 if (*requestor_sid == NULL)
366 goto enomem;
369 goto out;
371 enomem:
372 ret = krb5_enomem(context);
373 goto out;
375 out:
376 if (ret) {
377 krb5_free_principal(context, *canon_principal);
378 *canon_principal = NULL;
380 if (requestor_sid) {
381 kdc_object_release(*requestor_sid);
382 *requestor_sid = NULL;
386 ldap_value_free_len(values);
387 ldap_msgfree(m);
388 ldap_memfree(basedn);
389 free(filter);
390 gss_release_buffer(&minor, &initiator_name_buf);
392 return ret;
395 static KRB5_LIB_CALL krb5_error_code
396 authorize(void *ctx,
397 astgs_request_t r,
398 gss_const_name_t initiator_name,
399 gss_const_OID mech_type,
400 OM_uint32 ret_flags,
401 krb5_boolean *authorized,
402 krb5_principal *mapped_name)
404 struct altsecid_gss_preauth_authorizer_context *c = ctx;
405 struct ad_server_tuple *server = NULL;
406 krb5_error_code ret;
407 krb5_context context = kdc_request_get_context((kdc_request_t)r);
408 const hdb_entry *client = kdc_request_get_client(r);
409 krb5_const_principal server_princ = kdc_request_get_server_princ(r);
410 krb5_const_realm realm = krb5_principal_get_realm(context, client->principal);
411 krb5_boolean reconnect_p = FALSE;
412 krb5_boolean is_tgs;
413 kdc_data_t requestor_sid = NULL;
415 *authorized = FALSE;
416 *mapped_name = NULL;
418 if (!krb5_principal_is_federated(context, client->principal) ||
419 (ret_flags & GSS_C_ANON_FLAG))
420 return KRB5_PLUGIN_NO_HANDLE;
422 is_tgs = krb5_principal_is_krbtgt(context, server_princ);
424 HEIM_TAILQ_FOREACH(server, &c->servers, link) {
425 if (strcmp(realm, server->realm) == 0)
426 break;
429 if (server == NULL) {
430 server = calloc(1, sizeof(*server));
431 if (server == NULL)
432 return krb5_enomem(context);
434 server->realm = strdup(realm);
435 if (server->realm == NULL) {
436 free(server);
437 return krb5_enomem(context);
440 HEIM_TAILQ_INSERT_HEAD(&c->servers, server, link);
443 do {
444 if (server->ld == NULL) {
445 ret = ad_connect(context, realm, server);
446 if (ret)
447 return ret;
450 ret = ad_lookup(context, realm, server,
451 initiator_name, mech_type,
452 mapped_name, is_tgs ? &requestor_sid : NULL);
453 if (ret == KRB5KDC_ERR_SVC_UNAVAILABLE) {
454 ldap_unbind_ext_s(server->ld, NULL, NULL);
455 server->ld = NULL;
457 /* try to reconnect iff we haven't already tried */
458 reconnect_p = !reconnect_p;
461 *authorized = (ret == 0);
462 } while (reconnect_p);
464 if (requestor_sid) {
465 kdc_request_set_attribute((kdc_request_t)r,
466 HSTR("org.h5l.gss-pa-requestor-sid"), requestor_sid);
467 kdc_object_release(requestor_sid);
470 return ret;
473 static KRB5_LIB_CALL krb5_error_code
474 finalize_pac(void *ctx, astgs_request_t r)
476 kdc_data_t requestor_sid;
478 requestor_sid = kdc_request_get_attribute((kdc_request_t)r,
479 HSTR("org.h5l.gss-pa-requestor-sid"));
480 if (requestor_sid == NULL)
481 return 0;
483 kdc_audit_setkv_object((kdc_request_t)r, "gss_requestor_sid", requestor_sid);
485 return kdc_request_add_pac_buffer(r, PAC_REQUESTOR_SID,
486 kdc_data_get_data(requestor_sid));
489 static KRB5_LIB_CALL krb5_error_code
490 init(krb5_context context, void **contextp)
492 struct altsecid_gss_preauth_authorizer_context *c;
494 c = calloc(1, sizeof(*c));
495 if (c == NULL)
496 return krb5_enomem(context);
498 HEIM_TAILQ_INIT(&c->servers);
500 *contextp = c;
501 return 0;
504 static KRB5_LIB_CALL void
505 fini(void *context)
507 struct altsecid_gss_preauth_authorizer_context *c = context;
508 struct ad_server_tuple *server, *next;
509 OM_uint32 minor;
511 HEIM_TAILQ_FOREACH_SAFE(server, &c->servers, link, next) {
512 free(server->realm);
513 ldap_unbind_ext_s(server->ld, NULL, NULL);
514 #ifdef LDAP_OPT_X_SASL_GSS_CREDS
515 gss_release_cred(&minor, &server->gss_cred);
516 #endif
517 free(server);
521 static krb5plugin_gss_preauth_authorizer_ftable plug_desc =
522 { 1, init, fini, authorize, finalize_pac };
524 static krb5plugin_gss_preauth_authorizer_ftable *plugs[] = { &plug_desc };
526 static uintptr_t
527 altsecid_gss_preauth_authorizer_get_instance(const char *libname)
529 if (strcmp(libname, "krb5") == 0)
530 return krb5_get_instance(libname);
531 if (strcmp(libname, "kdc") == 0)
532 return kdc_get_instance(libname);
533 return 0;
536 krb5_plugin_load_ft kdc_gss_preauth_authorizer_plugin_load;
538 krb5_error_code KRB5_CALLCONV
539 kdc_gss_preauth_authorizer_plugin_load(heim_pcontext context,
540 krb5_get_instance_func_t *get_instance,
541 size_t *num_plugins,
542 krb5_plugin_common_ftable_cp **plugins)
544 *get_instance = altsecid_gss_preauth_authorizer_get_instance;
545 *num_plugins = sizeof(plugs) / sizeof(plugs[0]);
546 *plugins = (krb5_plugin_common_ftable_cp *)plugs;
547 return 0;