1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/os/hostrealm.c - realm-of-host and default-realm APIs */
3 /*
4 * Copyright (C) 2013 by the Massachusetts Institute of Technology.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 * * Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 *
14 * * Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in
16 * the documentation and/or other materials provided with the
17 * distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
24 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
30 * OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33 #include "k5-int.h"
34 #include "os-proto.h"
35 #include "fake-addrinfo.h"
36 #include <krb5/hostrealm_plugin.h>
37 #include <ctype.h>
38
39 #if defined(_WIN32) && !defined(__CYGWIN32__)
40 #ifndef EAFNOSUPPORT
41 #define EAFNOSUPPORT WSAEAFNOSUPPORT
42 #endif
43 #endif
44
45 struct hostrealm_module_handle {
46 struct krb5_hostrealm_vtable_st vt;
47 krb5_hostrealm_moddata data;
48 };
49
50 /* Release a list of hostrealm module handles. */
51 static void
free_handles(krb5_context context,struct hostrealm_module_handle ** handles)52 free_handles(krb5_context context, struct hostrealm_module_handle **handles)
53 {
54 struct hostrealm_module_handle *h, **hp;
55
56 if (handles == NULL)
57 return;
58 for (hp = handles; *hp != NULL; hp++) {
59 h = *hp;
60 if (h->vt.fini != NULL)
61 h->vt.fini(context, h->data);
62 free(h);
63 }
64 free(handles);
65 }
66
67 /* Get the registered hostrealm modules including all built-in modules, in the
68 * proper order. */
69 static krb5_error_code
get_modules(krb5_context context,krb5_plugin_initvt_fn ** modules_out)70 get_modules(krb5_context context, krb5_plugin_initvt_fn **modules_out)
71 {
72 krb5_error_code ret;
73 const int intf = PLUGIN_INTERFACE_HOSTREALM;
74
75 *modules_out = NULL;
76
77 /* Register built-in modules. */
78 ret = k5_plugin_register(context, intf, "registry",
79 hostrealm_registry_initvt);
80 if (ret)
81 return ret;
82 ret = k5_plugin_register(context, intf, "profile",
83 hostrealm_profile_initvt);
84 if (ret)
85 return ret;
86 ret = k5_plugin_register(context, intf, "dns", hostrealm_dns_initvt);
87 if (ret)
88 return ret;
89 ret = k5_plugin_register(context, intf, "domain", hostrealm_domain_initvt);
90 if (ret)
91 return ret;
92
93 return k5_plugin_load_all(context, intf, modules_out);
94 }
95
96 /* Initialize context->hostrealm_handles with a list of module handles. */
97 static krb5_error_code
load_hostrealm_modules(krb5_context context)98 load_hostrealm_modules(krb5_context context)
99 {
100 krb5_error_code ret;
101 struct hostrealm_module_handle **list = NULL, *handle;
102 krb5_plugin_initvt_fn *modules = NULL, *mod;
103 size_t count;
104
105 ret = get_modules(context, &modules);
106 if (ret != 0)
107 goto cleanup;
108
109 /* Allocate a large enough list of handles. */
110 for (count = 0; modules[count] != NULL; count++);
111 list = k5alloc((count + 1) * sizeof(*list), &ret);
112 if (list == NULL)
113 goto cleanup;
114
115 /* Initialize each module, ignoring ones that fail. */
116 count = 0;
117 for (mod = modules; *mod != NULL; mod++) {
118 handle = k5alloc(sizeof(*handle), &ret);
119 if (handle == NULL)
120 goto cleanup;
121 ret = (*mod)(context, 1, 1, (krb5_plugin_vtable)&handle->vt);
122 if (ret != 0) {
123 TRACE_HOSTREALM_VTINIT_FAIL(context, ret);
124 free(handle);
125 continue;
126 }
127
128 handle->data = NULL;
129 if (handle->vt.init != NULL) {
130 ret = handle->vt.init(context, &handle->data);
131 if (ret != 0) {
132 TRACE_HOSTREALM_INIT_FAIL(context, handle->vt.name, ret);
133 free(handle);
134 continue;
135 }
136 }
137 list[count++] = handle;
138 list[count] = NULL;
139 }
140 list[count] = NULL;
141
142 ret = 0;
143 context->hostrealm_handles = list;
144 list = NULL;
145
146 cleanup:
147 k5_plugin_free_modules(context, modules);
148 free_handles(context, list);
149 return ret;
150 }
151
152 /* Invoke a module's host_realm method, if it has one. */
153 static krb5_error_code
host_realm(krb5_context context,struct hostrealm_module_handle * h,const char * host,char *** realms_out)154 host_realm(krb5_context context, struct hostrealm_module_handle *h,
155 const char *host, char ***realms_out)
156 {
157 if (h->vt.host_realm == NULL)
158 return KRB5_PLUGIN_NO_HANDLE;
159 return h->vt.host_realm(context, h->data, host, realms_out);
160 }
161
162 /* Invoke a module's fallback_realm method, if it has one. */
163 static krb5_error_code
fallback_realm(krb5_context context,struct hostrealm_module_handle * h,const char * host,char *** realms_out)164 fallback_realm(krb5_context context, struct hostrealm_module_handle *h,
165 const char *host, char ***realms_out)
166 {
167 if (h->vt.fallback_realm == NULL)
168 return KRB5_PLUGIN_NO_HANDLE;
169 return h->vt.fallback_realm(context, h->data, host, realms_out);
170 }
171
172 /* Invoke a module's default_realm method, if it has one. */
173 static krb5_error_code
default_realm(krb5_context context,struct hostrealm_module_handle * h,char *** realms_out)174 default_realm(krb5_context context, struct hostrealm_module_handle *h,
175 char ***realms_out)
176 {
177 if (h->vt.default_realm == NULL)
178 return KRB5_PLUGIN_NO_HANDLE;
179 return h->vt.default_realm(context, h->data, realms_out);
180 }
181
182 /* Invoke a module's free_list method. */
183 static void
free_list(krb5_context context,struct hostrealm_module_handle * h,char ** list)184 free_list(krb5_context context, struct hostrealm_module_handle *h,
185 char **list)
186 {
187 h->vt.free_list(context, h->data, list);
188 }
189
190 /* Copy a null-terminated list of strings. */
191 static krb5_error_code
copy_list(char ** in,char *** out)192 copy_list(char **in, char ***out)
193 {
194 size_t count, i;
195 char **list;
196
197 *out = NULL;
198 for (count = 0; in[count] != NULL; count++);
199 list = calloc(count + 1, sizeof(*list));
200 if (list == NULL)
201 return ENOMEM;
202 for (i = 0; i < count; i++) {
203 list[i] = strdup(in[i]);
204 if (list[i] == NULL) {
205 krb5_free_host_realm(NULL, list);
206 return ENOMEM;
207 }
208 }
209 *out = list;
210 return 0;
211 }
212
213 static krb5_error_code
translate_gai_error(int num)214 translate_gai_error(int num)
215 {
216 switch (num) {
217 #ifdef EAI_ADDRFAMILY
218 case EAI_ADDRFAMILY:
219 return EAFNOSUPPORT;
220 #endif
221 case EAI_AGAIN:
222 return EAGAIN;
223 case EAI_BADFLAGS:
224 return EINVAL;
225 case EAI_FAIL:
226 return KRB5_EAI_FAIL;
227 case EAI_FAMILY:
228 return EAFNOSUPPORT;
229 case EAI_MEMORY:
230 return ENOMEM;
231 #if defined(EAI_NODATA) && EAI_NODATA != EAI_NONAME
232 case EAI_NODATA:
233 return KRB5_EAI_NODATA;
234 #endif
235 case EAI_NONAME:
236 return KRB5_EAI_NONAME;
237 #if defined(EAI_OVERFLOW)
238 case EAI_OVERFLOW:
239 return EINVAL; /* XXX */
240 #endif
241 case EAI_SERVICE:
242 return KRB5_EAI_SERVICE;
243 case EAI_SOCKTYPE:
244 return EINVAL;
245 #ifdef EAI_SYSTEM
246 case EAI_SYSTEM:
247 return errno;
248 #endif
249 }
250 abort();
251 return -1;
252 }
253
254 /* Get the canonical form of the local host name, using forward
255 * canonicalization only. */
256 krb5_error_code
krb5int_get_fq_local_hostname(char ** hostname_out)257 krb5int_get_fq_local_hostname(char **hostname_out)
258 {
259 struct addrinfo *ai, hints;
260 char buf[MAXHOSTNAMELEN];
261 int err;
262
263 *hostname_out = NULL;
264
265 if (gethostname(buf, sizeof(buf)) == -1)
266 return SOCKET_ERRNO;
267
268 memset(&hints, 0, sizeof(hints));
269 hints.ai_flags = AI_CANONNAME | AI_ADDRCONFIG;
270 err = getaddrinfo(buf, NULL, &hints, &ai);
271 if (err)
272 return translate_gai_error(err);
273 if (ai->ai_canonname == NULL) {
274 freeaddrinfo(ai);
275 return KRB5_EAI_FAIL;
276 }
277 *hostname_out = strdup(ai->ai_canonname);
278 freeaddrinfo(ai);
279 return (*hostname_out == NULL) ? ENOMEM : 0;
280 }
281
282 static krb5_error_code
clean_hostname(krb5_context context,const char * host,char ** cleanname_out)283 clean_hostname(krb5_context context, const char *host, char **cleanname_out)
284 {
285 char *p, *cleanname;
286 krb5_error_code ret;
287 size_t l;
288
289 *cleanname_out = NULL;
290
291 if (host != NULL) {
292 cleanname = strdup(host);
293 if (cleanname == NULL)
294 return ENOMEM;
295 } else {
296 ret = krb5int_get_fq_local_hostname(&cleanname);
297 if (ret)
298 return ret;
299 }
300
301 /* Fold to lowercase. */
302 for (p = cleanname; *p; p++) {
303 if (isupper((unsigned char)*p))
304 *p = tolower((unsigned char)*p);
305 }
306
307 /* Strip off trailing dot. */
308 l = strlen(cleanname);
309 if (l > 0 && cleanname[l - 1] == '.')
310 cleanname[l - 1] = '\0';
311
312 *cleanname_out = cleanname;
313 return 0;
314 }
315
316 /* Return true if name appears to be an IPv4 or IPv6 address. */
317 krb5_boolean
k5_is_numeric_address(const char * name)318 k5_is_numeric_address(const char *name)
319 {
320 int ndots = 0;
321 const char *p;
322
323 /* If name contains only numbers and three dots, consider it to be an IPv4
324 * address. */
325 if (strspn(name, "01234567890.") == strlen(name)) {
326 for (p = name; *p; p++) {
327 if (*p == '.')
328 ndots++;
329 }
330 if (ndots == 3)
331 return TRUE;
332 }
333
334 /* If name contains a colon, consider it to be an IPv6 address. */
335 if (strchr(name, ':') != NULL)
336 return TRUE;
337
338 return FALSE;
339 }
340
341 /* Construct a one-element realm list containing a copy of realm. */
342 krb5_error_code
k5_make_realmlist(const char * realm,char *** realms_out)343 k5_make_realmlist(const char *realm, char ***realms_out)
344 {
345 char **realms;
346
347 *realms_out = NULL;
348 realms = calloc(2, sizeof(*realms));
349 if (realms == NULL)
350 return ENOMEM;
351 realms[0] = strdup(realm);
352 if (realms[0] == NULL) {
353 free(realms);
354 return ENOMEM;
355 }
356 *realms_out = realms;
357 return 0;
358 }
359
360 krb5_error_code KRB5_CALLCONV
krb5_get_host_realm(krb5_context context,const char * host,char *** realms_out)361 krb5_get_host_realm(krb5_context context, const char *host, char ***realms_out)
362 {
363 krb5_error_code ret;
364 struct hostrealm_module_handle **hp;
365 char **realms, *cleanname = NULL;
366
367 *realms_out = NULL;
368
369 if (context->hostrealm_handles == NULL) {
370 ret = load_hostrealm_modules(context);
371 if (ret)
372 goto cleanup;
373 }
374
375 ret = clean_hostname(context, host, &cleanname);
376 if (ret)
377 goto cleanup;
378
379 /* Give each module a chance to determine the host's realms. */
380 for (hp = context->hostrealm_handles; *hp != NULL; hp++) {
381 ret = host_realm(context, *hp, cleanname, &realms);
382 if (ret == 0) {
383 ret = copy_list(realms, realms_out);
384 free_list(context, *hp, realms);
385 goto cleanup;
386 } else if (ret != KRB5_PLUGIN_NO_HANDLE) {
387 goto cleanup;
388 }
389 }
390
391 /* Return a list containing the "referral realm" (an empty realm), as a
392 * cue to try referrals. */
393 ret = k5_make_realmlist(KRB5_REFERRAL_REALM, realms_out);
394
395 cleanup:
396 free(cleanname);
397 return ret;
398 }
399
400 krb5_error_code KRB5_CALLCONV
krb5_get_fallback_host_realm(krb5_context context,krb5_data * hdata,char *** realms_out)401 krb5_get_fallback_host_realm(krb5_context context, krb5_data *hdata,
402 char ***realms_out)
403 {
404 krb5_error_code ret;
405 struct hostrealm_module_handle **hp;
406 char **realms, *defrealm, *host, *cleanname = NULL;
407
408 *realms_out = NULL;
409
410 /* Convert hdata into a string and clean it. */
411 host = k5memdup0(hdata->data, hdata->length, &ret);
412 if (host == NULL)
413 goto cleanup;
414 ret = clean_hostname(context, host, &cleanname);
415 free(host);
416 if (ret)
417 goto cleanup;
418
419 if (context->hostrealm_handles == NULL) {
420 ret = load_hostrealm_modules(context);
421 if (ret)
422 goto cleanup;
423 }
424
425 /* Give each module a chance to determine the fallback realms. */
426 for (hp = context->hostrealm_handles; *hp != NULL; hp++) {
427 ret = fallback_realm(context, *hp, cleanname, &realms);
428 if (ret == 0) {
429 ret = copy_list(realms, realms_out);
430 free_list(context, *hp, realms);
431 goto cleanup;
432 } else if (ret != KRB5_PLUGIN_NO_HANDLE) {
433 goto cleanup;
434 }
435 }
436
437 /* Return a list containing the default realm. */
438 ret = krb5_get_default_realm(context, &defrealm);
439 if (ret)
440 goto cleanup;
441 ret = k5_make_realmlist(defrealm, realms_out);
442 krb5_free_default_realm(context, defrealm);
443
444 cleanup:
445 free(cleanname);
446 return ret;
447 }
448
449 krb5_error_code KRB5_CALLCONV
krb5_free_host_realm(krb5_context context,char * const * list)450 krb5_free_host_realm(krb5_context context, char *const *list)
451 {
452 char *const *p;
453
454 for (p = list; p != NULL && *p != NULL; p++)
455 free(*p);
456 free((char **)list);
457 return 0;
458 }
459
460 /* Get the system default realm using hostrealm modules. */
461 static krb5_error_code
get_default_realm(krb5_context context,char ** realm_out)462 get_default_realm(krb5_context context, char **realm_out)
463 {
464 krb5_error_code ret;
465 struct hostrealm_module_handle **hp;
466 char **realms;
467
468 *realm_out = NULL;
469 if (context->hostrealm_handles == NULL) {
470 ret = load_hostrealm_modules(context);
471 if (ret)
472 return ret;
473 }
474
475 /* Give each module a chance to determine the default realm. */
476 for (hp = context->hostrealm_handles; *hp != NULL; hp++) {
477 ret = default_realm(context, *hp, &realms);
478 if (ret == 0) {
479 if (*realms == NULL) {
480 ret = KRB5_CONFIG_NODEFREALM;
481 } else {
482 *realm_out = strdup(realms[0]);
483 if (*realm_out == NULL)
484 ret = ENOMEM;
485 }
486 free_list(context, *hp, realms);
487 return ret;
488 } else if (ret != KRB5_PLUGIN_NO_HANDLE) {
489 return ret;
490 }
491 }
492
493 return KRB5_CONFIG_NODEFREALM;
494 }
495
496 krb5_error_code KRB5_CALLCONV
krb5_get_default_realm(krb5_context context,char ** realm_out)497 krb5_get_default_realm(krb5_context context, char **realm_out)
498 {
499 krb5_error_code ret;
500
501 *realm_out = NULL;
502
503 if (context == NULL || context->magic != KV5M_CONTEXT)
504 return KV5M_CONTEXT;
505
506 if (context->default_realm == NULL) {
507 ret = get_default_realm(context, &context->default_realm);
508 if (ret)
509 return ret;
510 }
511 *realm_out = strdup(context->default_realm);
512 return (*realm_out == NULL) ? ENOMEM : 0;
513 }
514
515 krb5_error_code KRB5_CALLCONV
krb5_set_default_realm(krb5_context context,const char * realm)516 krb5_set_default_realm(krb5_context context, const char *realm)
517 {
518 if (context == NULL || context->magic != KV5M_CONTEXT)
519 return KV5M_CONTEXT;
520
521 if (context->default_realm != NULL) {
522 free(context->default_realm);
523 context->default_realm = NULL;
524 }
525
526 /* Allow the caller to clear the default realm setting by passing NULL. */
527 if (realm != NULL) {
528 context->default_realm = strdup(realm);
529 if (context->default_realm == NULL)
530 return ENOMEM;
531 }
532
533 return 0;
534 }
535
536 void KRB5_CALLCONV
krb5_free_default_realm(krb5_context context,char * realm)537 krb5_free_default_realm(krb5_context context, char *realm)
538 {
539 free(realm);
540 }
541
542 void
k5_hostrealm_free_context(krb5_context context)543 k5_hostrealm_free_context(krb5_context context)
544 {
545 free_handles(context, context->hostrealm_handles);
546 context->hostrealm_handles = NULL;
547 }
548