#include "k5-int.h"
#include "os-proto.h"
#include "fake-addrinfo.h"
#include <ctype.h>
#ifdef HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#if !defined(DEFAULT_RDNS_LOOKUP)
#define DEFAULT_RDNS_LOOKUP 1
#endif
static krb5_boolean
use_reverse_dns(krb5_context context)
{
krb5_error_code ret;
int value;
ret = profile_get_boolean(context->profile, KRB5_CONF_LIBDEFAULTS,
KRB5_CONF_RDNS, NULL, DEFAULT_RDNS_LOOKUP,
&value);
if (ret)
return DEFAULT_RDNS_LOOKUP;
return value;
}
static char *
qualify_shortname(krb5_context context, const char *host)
{
krb5_error_code ret;
char *fqdn = NULL, *prof_domain = NULL, *os_domain = NULL;
const char *domain;
ret = profile_get_string(context->profile, KRB5_CONF_LIBDEFAULTS,
KRB5_CONF_QUALIFY_SHORTNAME, NULL, NULL,
&prof_domain);
if (ret)
return NULL;
#ifdef KRB5_DNS_LOOKUP
if (prof_domain == NULL)
os_domain = k5_primary_domain();
#endif
domain = (prof_domain != NULL) ? prof_domain : os_domain;
if (domain != NULL && *domain != '\0') {
if (asprintf(&fqdn, "%s.%s", host, domain) < 0)
fqdn = NULL;
}
profile_release_string(prof_domain);
free(os_domain);
return fqdn;
}
static krb5_error_code
expand_hostname(krb5_context context, const char *host, krb5_boolean use_dns,
char **canonhost_out)
{
struct addrinfo *ai = NULL, hint;
char namebuf[NI_MAXHOST], *qualified = NULL, *copy, *p;
int err;
const char *canonhost;
*canonhost_out = NULL;
canonhost = host;
if (use_dns) {
memset(&hint, 0, sizeof(hint));
hint.ai_flags = AI_CANONNAME;
err = getaddrinfo(host, 0, &hint, &ai);
if (err == EAI_MEMORY)
goto cleanup;
if (!err && ai->ai_canonname != NULL)
canonhost = ai->ai_canonname;
if (!err && use_reverse_dns(context)) {
err = getnameinfo(ai->ai_addr, ai->ai_addrlen, namebuf,
sizeof(namebuf), NULL, 0, NI_NAMEREQD);
if (err == EAI_MEMORY)
goto cleanup;
if (!err)
canonhost = namebuf;
}
}
if (canonhost == host && strchr(host, '.') == NULL) {
qualified = qualify_shortname(context, host);
if (qualified != NULL)
canonhost = qualified;
}
copy = strdup(canonhost);
if (copy == NULL)
goto cleanup;
for (p = copy; *p != '\0'; p++) {
if (isupper((unsigned char)*p))
*p = tolower((unsigned char)*p);
}
if (copy[0] != '\0') {
p = copy + strlen(copy) - 1;
if (*p == '.')
*p = '\0';
}
*canonhost_out = copy;
cleanup:
if (ai != NULL)
freeaddrinfo(ai);
free(qualified);
return (*canonhost_out == NULL) ? ENOMEM : 0;
}
krb5_error_code KRB5_CALLCONV
krb5_expand_hostname(krb5_context context, const char *host,
char **canonhost_out)
{
int use_dns = (context->dns_canonicalize_hostname == CANONHOST_TRUE);
return expand_hostname(context, host, use_dns, canonhost_out);
}
static void
split_trailer(const krb5_data *data, krb5_data *host, krb5_data *trailer)
{
char *p = memchr(data->data, ':', data->length);
unsigned int tlen = (p == NULL) ? 0 : data->length - (p - data->data);
if (p == NULL || tlen == 1 || memchr(p + 1, ':', tlen - 1) != NULL) {
*host = *data;
*trailer = make_data("", 0);
} else {
*host = make_data(data->data, p - data->data);
*trailer = make_data(p, tlen);
}
}
static krb5_error_code
canonicalize_princ(krb5_context context, struct canonprinc *iter,
krb5_boolean use_dns, krb5_const_principal *princ_out)
{
krb5_error_code ret;
krb5_data host, trailer;
char *hostname = NULL, *canonhost = NULL, *combined = NULL;
char **hrealms = NULL;
*princ_out = NULL;
assert(iter->princ->length == 2);
split_trailer(&iter->princ->data[1], &host, &trailer);
hostname = k5memdup0(host.data, host.length, &ret);
if (hostname == NULL)
goto cleanup;
if (iter->princ->type == KRB5_NT_SRV_HST) {
ret = expand_hostname(context, hostname, use_dns, &canonhost);
if (ret)
goto cleanup;
} else {
canonhost = strdup(hostname);
if (canonhost == NULL) {
ret = ENOMEM;
goto cleanup;
}
}
if (asprintf(&combined, "%s%.*s", canonhost,
trailer.length, trailer.data) < 0) {
combined = NULL;
ret = ENOMEM;
goto cleanup;
}
if (iter->canonhost != NULL && strcmp(iter->canonhost, combined) == 0)
goto cleanup;
free(iter->canonhost);
iter->canonhost = combined;
combined = NULL;
if (iter->princ->realm.length == 0 && !iter->no_hostrealm) {
ret = krb5_get_host_realm(context, canonhost, &hrealms);
if (ret)
goto cleanup;
if (hrealms[0] == NULL) {
ret = KRB5_ERR_HOST_REALM_UNKNOWN;
goto cleanup;
}
free(iter->realm);
if (*hrealms[0] == '\0' && iter->subst_defrealm) {
ret = krb5_get_default_realm(context, &iter->realm);
if (ret)
goto cleanup;
} else {
iter->realm = strdup(hrealms[0]);
if (iter->realm == NULL) {
ret = ENOMEM;
goto cleanup;
}
}
}
iter->copy = *iter->princ;
if (iter->realm != NULL)
iter->copy.realm = string2data(iter->realm);
iter->components[0] = iter->princ->data[0];
iter->components[1] = string2data(iter->canonhost);
iter->copy.data = iter->components;
*princ_out = &iter->copy;
cleanup:
free(hostname);
free(canonhost);
free(combined);
krb5_free_host_realm(context, hrealms);
return ret;
}
krb5_error_code
k5_canonprinc(krb5_context context, struct canonprinc *iter,
krb5_const_principal *princ_out)
{
krb5_error_code ret;
int step = ++iter->step;
*princ_out = NULL;
if (iter->princ->type != KRB5_NT_SRV_HST || iter->princ->length != 2 ||
iter->princ->data[1].length == 0) {
*princ_out = (step == 1) ? iter->princ : NULL;
return 0;
}
if (context->dns_canonicalize_hostname != CANONHOST_FALLBACK) {
if (step > 1)
return 0;
iter->copy = *iter->princ;
if (iter->subst_defrealm && iter->copy.realm.length == 0) {
ret = krb5_get_default_realm(context, &iter->realm);
if (ret)
return ret;
iter->copy = *iter->princ;
iter->copy.realm = string2data(iter->realm);
}
*princ_out = &iter->copy;
return 0;
}
if (step > 2)
return 0;
return canonicalize_princ(context, iter, step == 2, princ_out);
}
krb5_boolean
k5_sname_compare(krb5_context context, krb5_const_principal sname,
krb5_const_principal princ)
{
krb5_error_code ret;
struct canonprinc iter = { sname, .subst_defrealm = TRUE };
krb5_const_principal canonprinc = NULL;
krb5_boolean match = FALSE;
while ((ret = k5_canonprinc(context, &iter, &canonprinc)) == 0 &&
canonprinc != NULL) {
if (krb5_principal_compare(context, canonprinc, princ)) {
match = TRUE;
break;
}
}
free_canonprinc(&iter);
return match;
}
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)
{
krb5_error_code ret;
krb5_principal princ;
krb5_const_principal cprinc;
krb5_boolean use_dns;
char localname[MAXHOSTNAMELEN];
struct canonprinc iter = { NULL };
*princ_out = NULL;
if (type != KRB5_NT_UNKNOWN && type != KRB5_NT_SRV_HST)
return KRB5_SNAME_UNSUPP_NAMETYPE;
if (hostname == NULL) {
if (gethostname(localname, MAXHOSTNAMELEN) != 0)
return SOCKET_ERRNO;
hostname = localname;
}
if (sname == NULL)
sname = "host";
ret = krb5_build_principal(context, &princ, 0, KRB5_REFERRAL_REALM,
sname, hostname, (char *)NULL);
if (ret)
return ret;
princ->type = type;
if (type == KRB5_NT_SRV_HST &&
context->dns_canonicalize_hostname == CANONHOST_FALLBACK) {
*princ_out = princ;
return 0;
}
use_dns = (context->dns_canonicalize_hostname == CANONHOST_TRUE);
iter.princ = princ;
ret = canonicalize_princ(context, &iter, use_dns, &cprinc);
if (!ret)
ret = krb5_copy_principal(context, cprinc, princ_out);
free_canonprinc(&iter);
krb5_free_principal(context, princ);
return ret;
}