xref: /freebsd/crypto/krb5/src/lib/krb5/os/sn2princ.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/os/sn2princ.c */
3 /*
4  * Copyright 1991,2002 by the Massachusetts Institute of Technology.
5  * All Rights Reserved.
6  *
7  * Export of this software from the United States of America may
8  *   require a specific license from the United States Government.
9  *   It is the responsibility of any person or organization contemplating
10  *   export to obtain such a license before exporting.
11  *
12  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
13  * distribute this software and its documentation for any purpose and
14  * without fee is hereby granted, provided that the above copyright
15  * notice appear in all copies and that both that copyright notice and
16  * this permission notice appear in supporting documentation, and that
17  * the name of M.I.T. not be used in advertising or publicity pertaining
18  * to distribution of the software without specific, written prior
19  * permission.  Furthermore if you modify this software you must label
20  * your software as modified software and not distribute it in such a
21  * fashion that it might be confused with the original M.I.T. software.
22  * M.I.T. makes no representations about the suitability of
23  * this software for any purpose.  It is provided "as is" without express
24  * or implied warranty.
25  */
26 
27 /* Convert a hostname and service name to a principal in the "standard"
28  * form. */
29 
30 #include "k5-int.h"
31 #include "os-proto.h"
32 #include "fake-addrinfo.h"
33 #include <ctype.h>
34 #ifdef HAVE_SYS_PARAM_H
35 #include <sys/param.h>
36 #endif
37 
38 #if !defined(DEFAULT_RDNS_LOOKUP)
39 #define DEFAULT_RDNS_LOOKUP 1
40 #endif
41 
42 static krb5_boolean
use_reverse_dns(krb5_context context)43 use_reverse_dns(krb5_context context)
44 {
45     krb5_error_code ret;
46     int value;
47 
48     ret = profile_get_boolean(context->profile, KRB5_CONF_LIBDEFAULTS,
49                               KRB5_CONF_RDNS, NULL, DEFAULT_RDNS_LOOKUP,
50                               &value);
51     if (ret)
52         return DEFAULT_RDNS_LOOKUP;
53 
54     return value;
55 }
56 
57 /* Append a domain suffix to host and return the result in allocated memory.
58  * Return NULL if no suffix is configured or on failure. */
59 static char *
qualify_shortname(krb5_context context,const char * host)60 qualify_shortname(krb5_context context, const char *host)
61 {
62     krb5_error_code ret;
63     char *fqdn = NULL, *prof_domain = NULL, *os_domain = NULL;
64     const char *domain;
65 
66     ret = profile_get_string(context->profile, KRB5_CONF_LIBDEFAULTS,
67                              KRB5_CONF_QUALIFY_SHORTNAME, NULL, NULL,
68                              &prof_domain);
69     if (ret)
70         return NULL;
71 
72 #ifdef KRB5_DNS_LOOKUP
73     if (prof_domain == NULL)
74         os_domain = k5_primary_domain();
75 #endif
76 
77     domain = (prof_domain != NULL) ? prof_domain : os_domain;
78     if (domain != NULL && *domain != '\0') {
79         if (asprintf(&fqdn, "%s.%s", host, domain) < 0)
80             fqdn = NULL;
81     }
82 
83     profile_release_string(prof_domain);
84     free(os_domain);
85     return fqdn;
86 }
87 
88 static krb5_error_code
expand_hostname(krb5_context context,const char * host,krb5_boolean use_dns,char ** canonhost_out)89 expand_hostname(krb5_context context, const char *host, krb5_boolean use_dns,
90                 char **canonhost_out)
91 {
92     struct addrinfo *ai = NULL, hint;
93     char namebuf[NI_MAXHOST], *qualified = NULL, *copy, *p;
94     int err;
95     const char *canonhost;
96 
97     *canonhost_out = NULL;
98 
99     canonhost = host;
100     if (use_dns) {
101         /* Try a forward lookup of the hostname. */
102         memset(&hint, 0, sizeof(hint));
103         hint.ai_flags = AI_CANONNAME;
104         err = getaddrinfo(host, 0, &hint, &ai);
105         if (err == EAI_MEMORY)
106             goto cleanup;
107         if (!err && ai->ai_canonname != NULL)
108             canonhost = ai->ai_canonname;
109 
110         if (!err && use_reverse_dns(context)) {
111             /* Try a reverse lookup of the address. */
112             err = getnameinfo(ai->ai_addr, ai->ai_addrlen, namebuf,
113                               sizeof(namebuf), NULL, 0, NI_NAMEREQD);
114             if (err == EAI_MEMORY)
115                 goto cleanup;
116             if (!err)
117                 canonhost = namebuf;
118         }
119     }
120 
121     /* If we didn't use DNS and the name is just one component, try to add a
122      * domain suffix. */
123     if (canonhost == host && strchr(host, '.') == NULL) {
124         qualified = qualify_shortname(context, host);
125         if (qualified != NULL)
126             canonhost = qualified;
127     }
128 
129     copy = strdup(canonhost);
130     if (copy == NULL)
131         goto cleanup;
132 
133     /* Convert the hostname to lower case. */
134     for (p = copy; *p != '\0'; p++) {
135         if (isupper((unsigned char)*p))
136             *p = tolower((unsigned char)*p);
137     }
138 
139     /* Remove any trailing dot. */
140     if (copy[0] != '\0') {
141         p = copy + strlen(copy) - 1;
142         if (*p == '.')
143             *p = '\0';
144     }
145 
146     *canonhost_out = copy;
147 
148 cleanup:
149     /* We only return success or ENOMEM. */
150     if (ai != NULL)
151         freeaddrinfo(ai);
152     free(qualified);
153     return (*canonhost_out == NULL) ? ENOMEM : 0;
154 }
155 
156 krb5_error_code KRB5_CALLCONV
krb5_expand_hostname(krb5_context context,const char * host,char ** canonhost_out)157 krb5_expand_hostname(krb5_context context, const char *host,
158                      char **canonhost_out)
159 {
160     int use_dns = (context->dns_canonicalize_hostname == CANONHOST_TRUE);
161 
162     return expand_hostname(context, host, use_dns, canonhost_out);
163 }
164 
165 /* Split data into hostname and trailer (:port or :instance).  Trailers are
166  * used in MSSQLSvc principals. */
167 static void
split_trailer(const krb5_data * data,krb5_data * host,krb5_data * trailer)168 split_trailer(const krb5_data *data, krb5_data *host, krb5_data *trailer)
169 {
170     char *p = memchr(data->data, ':', data->length);
171     unsigned int tlen = (p == NULL) ? 0 : data->length - (p - data->data);
172 
173     /* Make sure we have a single colon followed by one or more characters.  An
174      * IPv6 address will have more than one colon, so don't accept that. */
175     if (p == NULL || tlen == 1 || memchr(p + 1, ':', tlen - 1) != NULL) {
176         *host = *data;
177         *trailer = make_data("", 0);
178     } else {
179         *host = make_data(data->data, p - data->data);
180         *trailer = make_data(p, tlen);
181     }
182 }
183 
184 static krb5_error_code
canonicalize_princ(krb5_context context,struct canonprinc * iter,krb5_boolean use_dns,krb5_const_principal * princ_out)185 canonicalize_princ(krb5_context context, struct canonprinc *iter,
186                    krb5_boolean use_dns, krb5_const_principal *princ_out)
187 {
188     krb5_error_code ret;
189     krb5_data host, trailer;
190     char *hostname = NULL, *canonhost = NULL, *combined = NULL;
191     char **hrealms = NULL;
192 
193     *princ_out = NULL;
194 
195     assert(iter->princ->length == 2);
196     split_trailer(&iter->princ->data[1], &host, &trailer);
197 
198     hostname = k5memdup0(host.data, host.length, &ret);
199     if (hostname == NULL)
200         goto cleanup;
201 
202     if (iter->princ->type == KRB5_NT_SRV_HST) {
203         /* Expand the hostname with or without DNS as specified. */
204         ret = expand_hostname(context, hostname, use_dns, &canonhost);
205         if (ret)
206             goto cleanup;
207     } else {
208         canonhost = strdup(hostname);
209         if (canonhost == NULL) {
210             ret = ENOMEM;
211             goto cleanup;
212         }
213     }
214 
215     /* Add the trailer to the expanded hostname. */
216     if (asprintf(&combined, "%s%.*s", canonhost,
217                  trailer.length, trailer.data) < 0) {
218         combined = NULL;
219         ret = ENOMEM;
220         goto cleanup;
221     }
222 
223     /* Don't yield the same host part twice. */
224     if (iter->canonhost != NULL && strcmp(iter->canonhost, combined) == 0)
225         goto cleanup;
226 
227     free(iter->canonhost);
228     iter->canonhost = combined;
229     combined = NULL;
230 
231     /* If the realm is unknown, look up the realm of the expanded hostname. */
232     if (iter->princ->realm.length == 0 && !iter->no_hostrealm) {
233         ret = krb5_get_host_realm(context, canonhost, &hrealms);
234         if (ret)
235             goto cleanup;
236         if (hrealms[0] == NULL) {
237             ret = KRB5_ERR_HOST_REALM_UNKNOWN;
238             goto cleanup;
239         }
240         free(iter->realm);
241         if (*hrealms[0] == '\0' && iter->subst_defrealm) {
242             ret = krb5_get_default_realm(context, &iter->realm);
243             if (ret)
244                 goto cleanup;
245         } else {
246             iter->realm = strdup(hrealms[0]);
247             if (iter->realm == NULL) {
248                 ret = ENOMEM;
249                 goto cleanup;
250             }
251         }
252     }
253 
254     iter->copy = *iter->princ;
255     if (iter->realm != NULL)
256         iter->copy.realm = string2data(iter->realm);
257     iter->components[0] = iter->princ->data[0];
258     iter->components[1] = string2data(iter->canonhost);
259     iter->copy.data = iter->components;
260     *princ_out = &iter->copy;
261 
262 cleanup:
263     free(hostname);
264     free(canonhost);
265     free(combined);
266     krb5_free_host_realm(context, hrealms);
267     return ret;
268 }
269 
270 krb5_error_code
k5_canonprinc(krb5_context context,struct canonprinc * iter,krb5_const_principal * princ_out)271 k5_canonprinc(krb5_context context, struct canonprinc *iter,
272               krb5_const_principal *princ_out)
273 {
274     krb5_error_code ret;
275     int step = ++iter->step;
276 
277     *princ_out = NULL;
278 
279     /* If the hostname isn't from krb5_sname_to_principal(), the input
280      * principal is canonical. */
281     if (iter->princ->type != KRB5_NT_SRV_HST || iter->princ->length != 2 ||
282         iter->princ->data[1].length == 0) {
283         *princ_out = (step == 1) ? iter->princ : NULL;
284         return 0;
285     }
286 
287     /* If we're not doing fallback, the hostname is canonical, but we may need
288      * to substitute the default realm. */
289     if (context->dns_canonicalize_hostname != CANONHOST_FALLBACK) {
290         if (step > 1)
291             return 0;
292         iter->copy = *iter->princ;
293         if (iter->subst_defrealm && iter->copy.realm.length == 0) {
294             ret = krb5_get_default_realm(context, &iter->realm);
295             if (ret)
296                 return ret;
297             iter->copy = *iter->princ;
298             iter->copy.realm = string2data(iter->realm);
299         }
300         *princ_out = &iter->copy;
301         return 0;
302     }
303 
304     /* Canonicalize without DNS at step 1, with DNS at step 2. */
305     if (step > 2)
306         return 0;
307     return canonicalize_princ(context, iter, step == 2, princ_out);
308 }
309 
310 krb5_boolean
k5_sname_compare(krb5_context context,krb5_const_principal sname,krb5_const_principal princ)311 k5_sname_compare(krb5_context context, krb5_const_principal sname,
312                  krb5_const_principal princ)
313 {
314     krb5_error_code ret;
315     struct canonprinc iter = { sname, .subst_defrealm = TRUE };
316     krb5_const_principal canonprinc = NULL;
317     krb5_boolean match = FALSE;
318 
319     while ((ret = k5_canonprinc(context, &iter, &canonprinc)) == 0 &&
320            canonprinc != NULL) {
321         if (krb5_principal_compare(context, canonprinc, princ)) {
322             match = TRUE;
323             break;
324         }
325     }
326     free_canonprinc(&iter);
327     return match;
328 }
329 
330 krb5_error_code KRB5_CALLCONV
krb5_sname_to_principal(krb5_context context,const char * hostname,const char * sname,krb5_int32 type,krb5_principal * princ_out)331 krb5_sname_to_principal(krb5_context context, const char *hostname,
332                         const char *sname, krb5_int32 type,
333                         krb5_principal *princ_out)
334 {
335     krb5_error_code ret;
336     krb5_principal princ;
337     krb5_const_principal cprinc;
338     krb5_boolean use_dns;
339     char localname[MAXHOSTNAMELEN];
340     struct canonprinc iter = { NULL };
341 
342     *princ_out = NULL;
343 
344     if (type != KRB5_NT_UNKNOWN && type != KRB5_NT_SRV_HST)
345         return KRB5_SNAME_UNSUPP_NAMETYPE;
346 
347     /* If hostname is NULL, use the local hostname. */
348     if (hostname == NULL) {
349         if (gethostname(localname, MAXHOSTNAMELEN) != 0)
350             return SOCKET_ERRNO;
351         hostname = localname;
352     }
353 
354     /* If sname is NULL, use "host". */
355     if (sname == NULL)
356         sname = "host";
357 
358     /* Build an initial principal with what we have. */
359     ret = krb5_build_principal(context, &princ, 0, KRB5_REFERRAL_REALM,
360                                sname, hostname, (char *)NULL);
361     if (ret)
362         return ret;
363     princ->type = type;
364 
365     if (type == KRB5_NT_SRV_HST &&
366         context->dns_canonicalize_hostname == CANONHOST_FALLBACK) {
367         /* Delay canonicalization and realm lookup until use. */
368         *princ_out = princ;
369         return 0;
370     }
371 
372     use_dns = (context->dns_canonicalize_hostname == CANONHOST_TRUE);
373     iter.princ = princ;
374     ret = canonicalize_princ(context, &iter, use_dns, &cprinc);
375     if (!ret)
376         ret = krb5_copy_principal(context, cprinc, princ_out);
377     free_canonprinc(&iter);
378     krb5_free_principal(context, princ);
379     return ret;
380 }
381