Imported Upstream version 1.17
[platform/upstream/krb5.git] / src / lib / krb5 / os / hostrealm.c
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
537 krb5_free_default_realm(krb5_context context, char *realm)
538 {
539     free(realm);
540 }
541
542 void
543 k5_hostrealm_free_context(krb5_context context)
544 {
545     free_handles(context, context->hostrealm_handles);
546     context->hostrealm_handles = NULL;
547 }