Unify access to FQDN

FreeIPA's Python and C code used different approaches to get the FQDN of
the host. Some places assumed that gethostname() returns a FQDN. Other
code paths used glibc's resolver to resolve the current node name to a
FQDN.

Python code now uses the ipalib.constants.FQDN where a fully qualified
domain name is expected. The variable is initialized only once and avoids
potential DNS lookups.

C code uses a new helper function ipa_gethostfqdn() in util package. The
function implements similar logic as gethostfqdn() except it uses more
modern getaddrinfo(). The result is cached as well.

Fixes: https://pagure.io/freeipa/issue/8501
Signed-off-by: Christian Heimes <cheimes@redhat.com>
Reviewed-By: Fraser Tweedale <ftweedal@redhat.com>
This commit is contained in:
Christian Heimes 2020-09-11 14:49:16 +02:00 committed by Fraser Tweedale
parent 5155280bb4
commit e28ec76898
23 changed files with 226 additions and 67 deletions

View File

@ -1,9 +1,11 @@
AM_CPPFLAGS := -I$(top_srcdir)/util
AM_CFLAGS := @LDAP_CFLAGS@ @LIBVERTO_CFLAGS@ @KRB5_CFLAGS@ @NSPR_CFLAGS@
AM_LDFLAGS := @LDAP_LIBS@ @LIBVERTO_LIBS@ @KRAD_LIBS@ @KRB5_LIBS@
noinst_HEADERS = internal.h
appdir = $(libexecdir)/ipa/
app_PROGRAMS = ipa-otpd
ipa_otpd_LDADD = $(top_builddir)/util/libutil.la
dist_noinst_DATA = ipa-otpd.socket.in ipa-otpd@.service.in test.py
systemdsystemunit_DATA = ipa-otpd.socket ipa-otpd@.service

View File

@ -32,6 +32,7 @@
#include <signal.h>
#include <stdbool.h>
#include "ipa_hostname.h"
/* Our global state. */
struct otpd_context ctx;
@ -212,7 +213,7 @@ static krb5_error_code setup_ldap(const char *uri, krb5_boolean bind,
int main(int argc, char **argv)
{
char hostname[HOST_NAME_MAX + 1];
char hostname[IPA_HOST_NAME_LEN];
krb5_error_code retval;
krb5_data hndata;
verto_ev *sig;
@ -227,7 +228,7 @@ int main(int argc, char **argv)
memset(&ctx, 0, sizeof(ctx));
ctx.exitstatus = 1;
if (gethostname(hostname, sizeof(hostname)) < 0) {
if (ipa_gethostfqdn(hostname) < 0) {
otpd_log_err(errno, "Unable to get hostname");
goto error;
}

View File

@ -36,6 +36,7 @@ char *smb_xstrdup(const char *s);
#include <sasl/sasl.h>
#include <krb5/krb5.h>
#include <sss_idmap.h>
#include "ipa_hostname.h"
#include "ipa_asn1.h"
#include "ipa_pwd.h"
#include "ipa_mspac.h"
@ -4440,7 +4441,7 @@ static char *sec_key(TALLOC_CTX *mem_ctx, const char *d)
static NTSTATUS save_sid_to_secret(struct ipasam_private *ipasam_state)
{
char hostname[255];
char hostname[IPA_HOST_NAME_LEN];
int ret;
char *p;
TALLOC_CTX *tmp_ctx;
@ -4466,13 +4467,12 @@ static NTSTATUS save_sid_to_secret(struct ipasam_private *ipasam_state)
goto done;
}
ret = gethostname(hostname, sizeof(hostname));
ret = ipa_gethostfqdn(hostname);
if (ret == -1) {
DEBUG(1, ("gethostname failed.\n"));
status = NT_STATUS_UNSUCCESSFUL;
goto done;
}
hostname[sizeof(hostname)-1] = '\0';
p = strchr(hostname, '.');
if (p != NULL) {
*p = '\0';
@ -4724,7 +4724,7 @@ static NTSTATUS ipasam_generate_principals(struct ipasam_private *ipasam_state)
int ret;
krb5_context context;
NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
char hostname[255];
char hostname[IPA_HOST_NAME_LEN];
char *default_realm = NULL;
if (!ipasam_state) {
@ -4736,12 +4736,11 @@ static NTSTATUS ipasam_generate_principals(struct ipasam_private *ipasam_state)
return status;
}
ret = gethostname(hostname, sizeof(hostname));
ret = ipa_gethostfqdn(hostname);
if (ret == -1) {
DEBUG(1, ("gethostname failed.\n"));
goto done;
}
hostname[sizeof(hostname)-1] = '\0';
rc = krb5_get_default_realm(context, &default_realm);
if (rc) {

View File

@ -5,6 +5,7 @@ PLUGIN_COMMON_DIR = $(srcdir)/../common
AM_CPPFLAGS = \
-I$(srcdir) \
-I$(PLUGIN_COMMON_DIR) \
-I$(top_srcdir)/util \
-DPREFIX=\""$(prefix)"\" \
-DBINDIR=\""$(bindir)"\" \
-DLIBDIR=\""$(libdir)"\" \
@ -31,6 +32,7 @@ libipa_cldap_la_SOURCES = \
libipa_cldap_la_LDFLAGS = -avoid-version
libipa_cldap_la_LIBADD = \
$(top_builddir)/util/libutil.la \
$(LDAP_LIBS) \
$(NDRNBT_LIBS) \
$(NULL)
@ -49,6 +51,7 @@ ipa_cldap_tests_LDFLAGS = \
-rpath $(shell pkg-config --libs-only-L dirsrv | sed -e 's/-L//') \
$(NULL)
ipa_cldap_tests_LDADD = \
$(top_builddir)/util/libutil.la \
$(CMOCKA_LIBS) \
$(NDRNBT_LIBS) \
$(DIRSRV_LIBS) \

View File

@ -38,6 +38,7 @@
* END COPYRIGHT BLOCK **/
#include "ipa_cldap.h"
#include "ipa_hostname.h"
#include <endian.h>
#include <talloc.h>
#include <ctype.h>
@ -236,7 +237,7 @@ int ipa_cldap_netlogon(struct ipa_cldap_ctx *ctx,
struct ipa_cldap_req *req,
struct berval *reply)
{
char hostname[MAXHOSTNAMELEN + 1]; /* NOTE: lenght hardcoded in kernel */
char hostname[IPA_HOST_NAME_LEN];
char *domain = NULL;
char *our_domain = NULL;
char *guid = NULL;
@ -321,13 +322,11 @@ int ipa_cldap_netlogon(struct ipa_cldap_ctx *ctx,
goto done;
}
ret = gethostname(hostname, MAXHOSTNAMELEN);
ret = ipa_gethostfqdn(hostname);
if (ret == -1) {
ret = errno;
goto done;
}
/* Make double sure it is terminated */
hostname[MAXHOSTNAMELEN] = '\0';
dot = strchr(hostname, '.');
if (!dot) {
/* this name is not fully qualified, therefore invalid */

View File

@ -30,6 +30,7 @@ from ipaplatform.paths import paths
from ipaserver.install import (replication, installutils, bindinstance,
cainstance)
from ipalib import api, errors
from ipalib.constants import FQDN
from ipalib.util import has_managed_topology
from ipapython import ipautil, ipaldap, version
from ipapython.admintool import ScriptError
@ -343,7 +344,7 @@ def re_initialize(realm, options):
if not options.fromhost:
sys.exit("re-initialize requires the option --from <host name>")
thishost = installutils.get_fqdn()
thishost = FQDN
try:
repl = replication.get_cs_replication_manager(realm, options.fromhost,
@ -383,7 +384,7 @@ def force_sync(realm, thishost, fromhost, dirman_passwd):
def set_renewal_master(realm, replica):
if not replica:
replica = installutils.get_fqdn()
replica = FQDN
ca = cainstance.CAInstance(realm)
if ca.is_renewal_master(replica):
@ -434,7 +435,7 @@ def main():
if options.host:
host = options.host
else:
host = installutils.get_fqdn()
host = FQDN
options.host = host

View File

@ -9,7 +9,6 @@ import argparse
import logging
import os
import platform
import socket
import warnings
from custodia.message.kem import KEY_USAGE_SIG, KEY_USAGE_ENC, KEY_USAGE_MAP
@ -136,7 +135,6 @@ class IPACustodiaTester:
def check(self):
self.status()
self.check_fqdn()
self.check_files()
self.check_client()
self.check_jwk()
@ -156,13 +154,6 @@ class IPACustodiaTester:
if self.host == self.args.server:
self.warning("Performing self-test only.")
def check_fqdn(self):
fqdn = socket.getfqdn()
if self.host != fqdn:
self.warning(
"socket.getfqdn() reports hostname '{}'".format(fqdn)
)
def check_files(self):
for filename in self.files:
if not os.path.isfile(filename):

View File

@ -28,6 +28,7 @@ from ipapython.dn import DN
from ipapython import version
from ipapython import ipautil, certdb
from ipalib import api, errors, x509
from ipalib.constants import FQDN
from ipaserver.install import installutils
# pylint: disable=deprecated-module
from optparse import OptionGroup, OptionValueError
@ -205,7 +206,7 @@ def parse_options():
parser.error("No action: you should select either --replica or --master option.")
if not options.hostname:
options.hostname = socket.getfqdn()
options.hostname = FQDN
return safe_options, options

View File

@ -38,6 +38,7 @@ from ipaserver.install import bindinstance, cainstance
from ipaserver.install import opendnssecinstance, dnskeysyncinstance
from ipapython import version, ipaldap
from ipalib import api, errors
from ipalib.constants import FQDN
from ipalib.util import has_managed_topology, verify_host_resolvable
from ipapython.ipa_log_manager import standard_logging_setup
from ipapython.dn import DN
@ -1525,7 +1526,7 @@ def main(options, args):
if options.host:
host = options.host
else:
host = installutils.get_fqdn()
host = FQDN
options.host = host

View File

@ -20,13 +20,13 @@
from __future__ import absolute_import
import logging
import socket
import six
from dns import rdatatype
from dns.exception import DNSException
from ipalib import errors
from ipalib.constants import FQDN
from ipalib.util import validate_domain_name
from ipapython.dnsutil import query_srv, resolve
@ -222,7 +222,7 @@ class IPADiscovery:
if not domain: # domain not provided do full DNS discovery
# get the local host name
if not hostname:
hostname = socket.getfqdn()
hostname = FQDN
logger.debug('Hostname: %s', hostname)
if not hostname:
return BAD_HOST_CONFIG

View File

@ -36,7 +36,7 @@ from urllib.parse import urlparse, urlunparse
from ipalib import api, errors, x509
from ipalib import sysrestore
from ipalib.constants import IPAAPI_USER, MAXHOSTNAMELEN
from ipalib.constants import FQDN, IPAAPI_USER, MAXHOSTNAMELEN
from ipalib.install import certmonger, certstore, service
from ipalib.install import hostname as hostname_
from ipalib.facts import is_ipa_client_configured, is_ipa_configured
@ -2121,7 +2121,7 @@ def install_check(options):
hostname = options.hostname
hostname_source = 'Provided as option'
else:
hostname = socket.getfqdn()
hostname = FQDN
hostname_source = "Machine's FQDN"
if hostname != hostname.lower():
raise ScriptError(
@ -3270,7 +3270,7 @@ def uninstall(options):
pass
if hostname is None:
hostname = socket.getfqdn()
hostname = FQDN
ipa_db = certdb.NSSDatabase(paths.IPA_NSSDB_DIR)
sys_db = certdb.NSSDatabase(paths.NSS_DB_DIR)

View File

@ -23,18 +23,14 @@ All constants centralised in one file.
"""
import os
import socket
from ipaplatform.constants import constants as _constants
from ipapython.dn import DN
from ipapython.fqdn import gethostfqdn
from ipapython.version import VERSION, API_VERSION
try:
FQDN = socket.getfqdn()
except Exception:
try:
FQDN = socket.gethostname()
except Exception:
FQDN = None
FQDN = gethostfqdn()
# TLS related constants
# * SSL2 and SSL3 are broken.

View File

@ -25,7 +25,6 @@ from optparse import (
from copy import copy
from configparser import SafeConfigParser
from urllib.parse import urlsplit
import socket
import functools
from dns.exception import DNSException
@ -211,8 +210,11 @@ def __discover_config(discover_server = True):
servers = query_srv(name)
except DNSException:
# try cycling on domain components of FQDN
# pylint: disable=ipa-forbidden-import
from ipalib.constants import FQDN
# pylint: enable=ipa-forbidden-import
try:
domain = dns.name.from_text(socket.getfqdn())
domain = dns.name.from_text(FQDN)
except DNSException:
return False

30
ipapython/fqdn.py Normal file
View File

@ -0,0 +1,30 @@
#
# Copyright (C) 2020 FreeIPA Contributors see COPYING for license
#
"""Get host's FQDN
"""
import socket
def gethostfqdn():
hostname = socket.gethostname()
# optional optimization, consider hostname with dot as FQDN
if "." in hostname:
return hostname
# this call can never fail except for misconfigured nsswitch.conf
# without nss-myhostname provider. The myhostname provider translates
# gethostname() to local interfaces.
gai = socket.getaddrinfo(
hostname,
None, # service/port is irrelevant
family=socket.AF_UNSPEC, # IPv4 or IPv6
type=socket.SOCK_DGRAM, # optimization, TCP/RAW gives same result
# include canonical name in first addrinfo struct
# only use address family when at least one non-local interface
# is configured with that address family
flags=socket.AI_CANONNAME | socket.AI_ADDRCONFIG
)
# first addrinfo struct, fourth field is canonical name
return gai[0][3]

View File

@ -30,11 +30,11 @@ import time
from ipalib import api, _
from ipalib import errors
from ipalib.constants import FQDN
from ipapython import ipautil
from ipapython.dn import DN
from ipapython.dnsutil import query_srv
from ipapython.ipaldap import ldap_initialize
from ipaserver.install import installutils
from ipaserver.dcerpc_common import (TRUST_BIDIRECTIONAL,
TRUST_JOIN_EXTERNAL,
trust_type_string)
@ -1645,7 +1645,7 @@ class TrustDomainJoins:
ld.creds.set_kerberos_state(credentials.MUST_USE_KERBEROS)
ld.creds.guess(ld.parm)
ld.creds.set_workstation(ld.hostname)
ld.retrieve(installutils.get_fqdn())
ld.retrieve(FQDN)
self.local_domain = ld
def populate_remote_domain(self, realm, realm_server=None,

View File

@ -50,7 +50,7 @@ import ipaplatform
from ipapython import ipautil, admintool, version, ipaldap
from ipapython.admintool import ScriptError, SERVER_NOT_CONFIGURED # noqa: E402
from ipapython.certdb import EXTERNAL_CA_TRUST_FLAGS
from ipalib.constants import MAXHOSTNAMELEN
from ipalib.constants import FQDN, MAXHOSTNAMELEN
from ipalib.util import validate_hostname
from ipalib import api, errors, x509
from ipalib.install import dnsforwarders
@ -118,16 +118,16 @@ class ReplicaConfig:
subject_base = ipautil.dn_attribute_property('_subject_base')
def get_fqdn():
fqdn = ""
try:
fqdn = socket.getfqdn()
except Exception:
try:
fqdn = socket.gethostname()
except Exception:
fqdn = ""
return fqdn
"""Get fully qualified domain name of current host
:note: used by ansible_freeipa
:deprecated: use ipalib.constants.FQDN
:return: str
"""
return FQDN
def verify_fqdn(host_name, no_host_dns=False, local_hostname=True):
"""

View File

@ -24,9 +24,9 @@ import ldap.schema
import ipapython.version
from ipalib import api
from ipalib.constants import FQDN
from ipapython.dn import DN
from ipaserver.install.ldapupdate import connect
from ipaserver.install import installutils
SCHEMA_ELEMENT_CLASSES = (
@ -105,9 +105,7 @@ def update_schema(schema_files, ldapi=False):
"""
SCHEMA_ELEMENT_CLASSES_KEYS = [x[0] for x in SCHEMA_ELEMENT_CLASSES]
conn = connect(ldapi=ldapi,
realm=api.env.realm,
fqdn=installutils.get_fqdn())
conn = connect(ldapi=ldapi, realm=api.env.realm, fqdn=FQDN)
old_schema = conn.schema

View File

@ -31,7 +31,7 @@ from ipaplatform import services
from ipaplatform.paths import paths
from ipaplatform.tasks import tasks
from ipalib import api, errors, x509
from ipalib.constants import DOMAIN_LEVEL_0
from ipalib.constants import DOMAIN_LEVEL_0, FQDN
from ipalib.facts import is_ipa_configured, is_ipa_client_configured
from ipalib.util import (
validate_domain_name,
@ -44,7 +44,7 @@ from ipaserver.install import (
otpdinstance, custodiainstance, replication, service,
sysupgrade, cainstance)
from ipaserver.install.installutils import (
BadHostError, get_fqdn, get_server_ip_address,
BadHostError, get_server_ip_address,
load_pkcs12, read_password, verify_fqdn, update_hosts_file,
validate_mask)
@ -493,7 +493,7 @@ def install_check(installer):
if options.host_name:
host_default = options.host_name
else:
host_default = get_fqdn()
host_default = FQDN
if installer.interactive and not options.host_name:
host_name = read_host_name(host_default)

View File

@ -22,7 +22,6 @@ from __future__ import absolute_import
import logging
import sys
import os
import socket
import time
import traceback
import tempfile
@ -35,6 +34,7 @@ from ipapython import ipautil
from ipapython.dn import DN
from ipapython import kerberos
from ipalib import api, errors, x509
from ipalib.constants import FQDN
from ipaplatform import services
from ipaplatform.constants import User
from ipaplatform.paths import paths
@ -291,7 +291,7 @@ class Service:
self.steps = []
self.output_fd = sys.stdout
self.fqdn = socket.gethostname()
self.fqdn = FQDN
if sstore:
self.sstore = sstore

View File

@ -28,8 +28,8 @@ import pytest
from ipalib import api
from ipalib import errors
from ipalib.constants import FQDN
from ipaserver.install.ldapupdate import LDAPUpdate, BadSyntax
from ipaserver.install import installutils
from ipapython import ipaldap
from ipaplatform.constants import constants as platformconstants
from ipapython.dn import DN
@ -56,7 +56,6 @@ class TestUpdate:
@pytest.fixture(autouse=True)
def update_setup(self, request):
fqdn = installutils.get_fqdn()
pwfile = api.env.dot_ipa + os.sep + ".dmpw"
if os.path.isfile(pwfile):
with open(pwfile, "r") as fp:
@ -64,7 +63,7 @@ class TestUpdate:
else:
pytest.skip("No directory manager password")
self.updater = LDAPUpdate()
self.ld = ipaldap.LDAPClient.from_hostname_secure(fqdn)
self.ld = ipaldap.LDAPClient.from_hostname_secure(FQDN)
self.ld.simple_bind(bind_dn=ipaldap.DIRMAN_DN,
bind_password=self.dm_password)
self.testdir = os.path.abspath(os.path.dirname(__file__))

View File

@ -1,17 +1,23 @@
NULL =
AUTOMAKE_OPTIONS = 1.7 subdir-objects
AM_CPPFLAGS = $(CRYPTO_CFLAGS) $(KRB5_CFLAGS) $(LDAP_CFLAGS) $(PWQUALITY_CFLAGS)
noinst_LTLIBRARIES = libutil.la
libutil_la_SOURCES = ipa_krb5.c \
libutil_la_SOURCES = \
ipa_hostname.c \
ipa_hostname.h \
ipa_krb5.c \
ipa_krb5.h \
ipa_mspac.h \
ipa_ldap.c \
ipa_ldap.h \
ipa_pwd.c \
ipa_pwd.h \
ipa_pwd_ntlm.c
ipa_pwd_ntlm.c \
$(NULL)
libutil_la_LIBADD = $(CRYPTO_LIBS) $(KRB5_LIBS) $(LDAP_LIBS) $(PWQUALITY_LIBS)

111
util/ipa_hostname.c Normal file
View File

@ -0,0 +1,111 @@
/*
* Copyright (C) 2020 FreeIPA Contributors see COPYING for license
*/
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <netdb.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "ipa_hostname.h"
int
ipa_gethostname(char *name)
{
int ret;
ret = gethostname(name, IPA_HOST_NAME_LEN - 1);
/* Make double sure it is terminated */
name[IPA_HOST_NAME_LEN - 1] = '\0';
return ret;
}
static int
_get_fqdn(char *fqdn)
{
char hostname[IPA_HOST_NAME_LEN];
char *canonname = NULL;
struct addrinfo hints;
struct addrinfo *ai = NULL;
int r;
r = ipa_gethostname(hostname);
if (r != 0) {
goto error;
}
memset(&hints, 0, sizeof(struct addrinfo));
/* use IPv4 or IPv6 */
hints.ai_family = AF_UNSPEC;
/* optimize, RAW and STREAM return same kind of information */
hints.ai_socktype = SOCK_DGRAM;
/* any protocol */
hints.ai_protocol = 0;
/* get canonical name
* only use IPv4/6 when at least one interface for proto is configured */
hints.ai_flags = AI_CANONNAME | AI_ADDRCONFIG;
r = getaddrinfo(hostname, NULL, &hints, &ai);
if (r != 0) {
/* getaddrinfo() for gethostname() should never fail. The
* nss-myhostname provider should always add a positive match. */
errno = ENOENT;
goto error;
}
/* only the first addrinfo struct holds a canonical name value */
canonname = ai->ai_canonname;
/* check that canon name is filled and not too long */
if (!canonname) {
errno = ENOENT;
goto error;
}
if (strlen(canonname) >= IPA_HOST_NAME_LEN) {
errno = ENAMETOOLONG;
goto error;
}
#if 0
/* refuse non-qualified short names and localhost */
if ((strchr(canonname, '.') == NULL) ||
(strcasecmp(canonname, "localhost.localdomain") == 0)) {
errno = EINVAL;
goto error;
}
#endif
strcpy(fqdn, canonname);
/* Make double sure it is terminated */
fqdn[IPA_HOST_NAME_LEN - 1] = '\0';
freeaddrinfo(ai);
return 0;
error:
fqdn[0] = '\0';
if (ai != NULL) {
freeaddrinfo(ai);
}
return -1;
}
int ipa_gethostfqdn(char *name)
{
static char cached_fqdn[IPA_HOST_NAME_LEN] = {0};
if (!cached_fqdn) {
int res = _get_fqdn(cached_fqdn);
if (res != 0) {
return -1;
}
}
strcpy(name, cached_fqdn);
return 0;
}

19
util/ipa_hostname.h Normal file
View File

@ -0,0 +1,19 @@
/*
* Copyright (C) 2020 FreeIPA Contributors see COPYING for license
*/
#include <limits.h>
#include <unistd.h>
/*
* host name length including NULL byte
*
* NOTE: length hardcoded in kernel
*/
#define IPA_HOST_NAME_LEN (HOST_NAME_MAX + 1)
int
ipa_gethostname(char *name);
int
ipa_gethostfqdn(char *name);