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