78d6c5df487a078625a8e44fa0aacab86d62b549
[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 *buf, size_t bufsize)
258 {
259     struct addrinfo *ai, hints;
260     int err;
261
262     buf[0] = '\0';
263     if (gethostname(buf, bufsize) == -1)
264         return SOCKET_ERRNO;
265
266     memset(&hints, 0, sizeof(hints));
267     hints.ai_flags = AI_CANONNAME | AI_ADDRCONFIG;
268     err = getaddrinfo(buf, NULL, &hints, &ai);
269     if (err)
270         return translate_gai_error(err);
271     if (ai->ai_canonname == NULL) {
272         freeaddrinfo(ai);
273         return KRB5_EAI_FAIL;
274     }
275     if (strlcpy(buf, ai->ai_canonname, bufsize) >= bufsize)
276         return ENOMEM;
277     freeaddrinfo(ai);
278     return 0;
279 }
280
281 krb5_error_code
282 k5_clean_hostname(krb5_context context, const char *host, char *cleanname,
283                   size_t lhsize)
284 {
285     char *p;
286     krb5_error_code ret;
287     size_t l;
288
289     cleanname[0] = '\0';
290     if (host != NULL) {
291         if (strlcpy(cleanname, host, lhsize) >= lhsize)
292             return ENOMEM;
293     } else {
294         ret = krb5int_get_fq_local_hostname(cleanname, lhsize);
295         if (ret)
296             return ret;
297     }
298
299     /* Fold to lowercase. */
300     for (p = cleanname; *p; p++) {
301         if (isupper((unsigned char)*p))
302             *p = tolower((unsigned char)*p);
303     }
304
305     /* Strip off trailing dot. */
306     l = strlen(cleanname);
307     if (l > 0 && cleanname[l - 1] == '.')
308         cleanname[l - 1] = '\0';
309
310     return 0;
311 }
312
313 /* Return true if name appears to be an IPv4 or IPv6 address. */
314 krb5_boolean
315 k5_is_numeric_address(const char *name)
316 {
317     int ndots = 0;
318     const char *p;
319
320     /* If name contains only numbers and three dots, consider it to be an IPv4
321      * address. */
322     if (strspn(name, "01234567890.") == strlen(name)) {
323         for (p = name; *p; p++) {
324             if (*p == '.')
325                 ndots++;
326         }
327         if (ndots == 3)
328             return TRUE;
329     }
330
331     /* If name contains a colon, consider it to be an IPv6 address. */
332     if (strchr(name, ':') != NULL)
333         return TRUE;
334
335     return FALSE;
336 }
337
338 /* Construct a one-element realm list containing a copy of realm. */
339 krb5_error_code
340 k5_make_realmlist(const char *realm, char ***realms_out)
341 {
342     char **realms;
343
344     *realms_out = NULL;
345     realms = calloc(2, sizeof(*realms));
346     if (realms == NULL)
347         return ENOMEM;
348     realms[0] = strdup(realm);
349     if (realms[0] == NULL) {
350         free(realms);
351         return ENOMEM;
352     }
353     *realms_out = realms;
354     return 0;
355 }
356
357 krb5_error_code KRB5_CALLCONV
358 krb5_get_host_realm(krb5_context context, const char *host, char ***realms_out)
359 {
360     krb5_error_code ret;
361     struct hostrealm_module_handle **hp;
362     char **realms, cleanname[1024];
363
364     *realms_out = NULL;
365
366     if (context->hostrealm_handles == NULL) {
367         ret = load_hostrealm_modules(context);
368         if (ret)
369             return ret;
370     }
371
372     ret = k5_clean_hostname(context, host, cleanname, sizeof(cleanname));
373     if (ret)
374         return ret;
375
376     /* Give each module a chance to determine the host's realms. */
377     for (hp = context->hostrealm_handles; *hp != NULL; hp++) {
378         ret = host_realm(context, *hp, cleanname, &realms);
379         if (ret == 0) {
380             ret = copy_list(realms, realms_out);
381             free_list(context, *hp, realms);
382             return ret;
383         } else if (ret != KRB5_PLUGIN_NO_HANDLE) {
384             return ret;
385         }
386     }
387
388     /* Return a list containing the "referral realm" (an empty realm), as a
389      * cue to try referrals. */
390     return k5_make_realmlist(KRB5_REFERRAL_REALM, realms_out);
391 }
392
393 krb5_error_code KRB5_CALLCONV
394 krb5_get_fallback_host_realm(krb5_context context, krb5_data *hdata,
395                              char ***realms_out)
396 {
397     krb5_error_code ret;
398     struct hostrealm_module_handle **hp;
399     char **realms, *defrealm, *host, cleanname[1024];
400
401     *realms_out = NULL;
402
403     /* Convert hdata into a string and clean it. */
404     host = k5memdup0(hdata->data, hdata->length, &ret);
405     if (host == NULL)
406         return ret;
407     ret = k5_clean_hostname(context, host, cleanname, sizeof(cleanname));
408     free(host);
409     if (ret)
410         return ret;
411
412     if (context->hostrealm_handles == NULL) {
413         ret = load_hostrealm_modules(context);
414         if (ret)
415             return ret;
416     }
417
418     /* Give each module a chance to determine the fallback realms. */
419     for (hp = context->hostrealm_handles; *hp != NULL; hp++) {
420         ret = fallback_realm(context, *hp, cleanname, &realms);
421         if (ret == 0) {
422             ret = copy_list(realms, realms_out);
423             free_list(context, *hp, realms);
424             return ret;
425         } else if (ret != KRB5_PLUGIN_NO_HANDLE) {
426             return ret;
427         }
428     }
429
430     /* Return a list containing the default realm. */
431     ret = krb5_get_default_realm(context, &defrealm);
432     if (ret)
433         return ret;
434     ret = k5_make_realmlist(defrealm, realms_out);
435     krb5_free_default_realm(context, defrealm);
436     return ret;
437 }
438
439 krb5_error_code KRB5_CALLCONV
440 krb5_free_host_realm(krb5_context context, char *const *list)
441 {
442     char *const *p;
443
444     for (p = list; p != NULL && *p != NULL; p++)
445         free(*p);
446     free((char **)list);
447     return 0;
448 }
449
450 /* Get the system default realm using hostrealm modules. */
451 static krb5_error_code
452 get_default_realm(krb5_context context, char **realm_out)
453 {
454     krb5_error_code ret;
455     struct hostrealm_module_handle **hp;
456     char **realms;
457
458     *realm_out = NULL;
459     if (context->hostrealm_handles == NULL) {
460         ret = load_hostrealm_modules(context);
461         if (ret)
462             return ret;
463     }
464
465     /* Give each module a chance to determine the default realm. */
466     for (hp = context->hostrealm_handles; *hp != NULL; hp++) {
467         ret = default_realm(context, *hp, &realms);
468         if (ret == 0) {
469             if (*realms == NULL) {
470                 ret = KRB5_CONFIG_NODEFREALM;
471             } else {
472                 *realm_out = strdup(realms[0]);
473                 if (*realm_out == NULL)
474                     ret = ENOMEM;
475             }
476             free_list(context, *hp, realms);
477             return ret;
478         } else if (ret != KRB5_PLUGIN_NO_HANDLE) {
479             return ret;
480         }
481     }
482
483     return KRB5_CONFIG_NODEFREALM;
484 }
485
486 krb5_error_code KRB5_CALLCONV
487 krb5_get_default_realm(krb5_context context, char **realm_out)
488 {
489     krb5_error_code ret;
490
491     *realm_out = NULL;
492
493     if (context == NULL || context->magic != KV5M_CONTEXT)
494         return KV5M_CONTEXT;
495
496     if (context->default_realm == NULL) {
497         ret = get_default_realm(context, &context->default_realm);
498         if (ret)
499             return ret;
500     }
501     *realm_out = strdup(context->default_realm);
502     return (*realm_out == NULL) ? ENOMEM : 0;
503 }
504
505 krb5_error_code KRB5_CALLCONV
506 krb5_set_default_realm(krb5_context context, const char *realm)
507 {
508     if (context == NULL || context->magic != KV5M_CONTEXT)
509         return KV5M_CONTEXT;
510
511     if (context->default_realm != NULL) {
512         free(context->default_realm);
513         context->default_realm = NULL;
514     }
515
516     /* Allow the caller to clear the default realm setting by passing NULL. */
517     if (realm != NULL) {
518         context->default_realm = strdup(realm);
519         if (context->default_realm == NULL)
520             return ENOMEM;
521     }
522
523     return 0;
524 }
525
526 void KRB5_CALLCONV
527 krb5_free_default_realm(krb5_context context, char *realm)
528 {
529     free(realm);
530 }
531
532 void
533 k5_hostrealm_free_context(krb5_context context)
534 {
535     free_handles(context, context->hostrealm_handles);
536     context->hostrealm_handles = NULL;
537 }