/* SPDX-License-Identifier: LGPL-2.1-or-later */ #include #include #include #include #include #include "alloc-util.h" #include "fd-util.h" #include "fileio.h" #include "hostname-util.h" #include "macro.h" #include "string-util.h" #include "strv.h" bool hostname_is_set(void) { struct utsname u; assert_se(uname(&u) >= 0); if (isempty(u.nodename)) return false; /* This is the built-in kernel default hostname */ if (streq(u.nodename, "(none)")) return false; return true; } char* gethostname_malloc(void) { struct utsname u; const char *s; /* This call tries to return something useful, either the actual hostname * or it makes something up. The only reason it might fail is OOM. * It might even return "localhost" if that's set. */ assert_se(uname(&u) >= 0); s = u.nodename; if (isempty(s) || streq(s, "(none)")) s = FALLBACK_HOSTNAME; return strdup(s); } char* gethostname_short_malloc(void) { struct utsname u; const char *s; /* Like above, but kills the FQDN part if present. */ assert_se(uname(&u) >= 0); s = u.nodename; if (isempty(s) || streq(s, "(none)") || s[0] == '.') { s = FALLBACK_HOSTNAME; assert(s[0] != '.'); } return strndup(s, strcspn(s, ".")); } int gethostname_strict(char **ret) { struct utsname u; char *k; /* This call will rather fail than make up a name. It will not return "localhost" either. */ assert_se(uname(&u) >= 0); if (isempty(u.nodename)) return -ENXIO; if (streq(u.nodename, "(none)")) return -ENXIO; if (is_localhost(u.nodename)) return -ENXIO; k = strdup(u.nodename); if (!k) return -ENOMEM; *ret = k; return 0; } bool valid_ldh_char(char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-'; } /** * Check if s looks like a valid hostname or FQDN. This does not do * full DNS validation, but only checks if the name is composed of * allowed characters and the length is not above the maximum allowed * by Linux (c.f. dns_name_is_valid()). Trailing dot is allowed if * allow_trailing_dot is true and at least two components are present * in the name. Note that due to the restricted charset and length * this call is substantially more conservative than * dns_name_is_valid(). */ bool hostname_is_valid(const char *s, bool allow_trailing_dot) { unsigned n_dots = 0; const char *p; bool dot, hyphen; if (isempty(s)) return false; /* Doesn't accept empty hostnames, hostnames with * leading dots, and hostnames with multiple dots in a * sequence. Also ensures that the length stays below * HOST_NAME_MAX. */ for (p = s, dot = hyphen = true; *p; p++) if (*p == '.') { if (dot || hyphen) return false; dot = true; hyphen = false; n_dots++; } else if (*p == '-') { if (dot) return false; dot = false; hyphen = true; } else { if (!valid_ldh_char(*p)) return false; dot = false; hyphen = false; } if (dot && (n_dots < 2 || !allow_trailing_dot)) return false; if (hyphen) return false; if (p-s > HOST_NAME_MAX) /* Note that HOST_NAME_MAX is 64 on * Linux, but DNS allows domain names * up to 255 characters */ return false; return true; } char* hostname_cleanup(char *s) { char *p, *d; bool dot, hyphen; assert(s); for (p = s, d = s, dot = hyphen = true; *p && d - s < HOST_NAME_MAX; p++) if (*p == '.') { if (dot || hyphen) continue; *(d++) = '.'; dot = true; hyphen = false; } else if (*p == '-') { if (dot) continue; *(d++) = '-'; dot = false; hyphen = true; } else if (valid_ldh_char(*p)) { *(d++) = *p; dot = false; hyphen = false; } if (d > s && IN_SET(d[-1], '-', '.')) /* The dot can occur at most once, but we might have multiple * hyphens, hence the loop */ d--; *d = 0; return s; } bool is_localhost(const char *hostname) { assert(hostname); /* This tries to identify local host and domain names * described in RFC6761 plus the redhatism of localdomain */ return STRCASE_IN_SET( hostname, "localhost", "localhost.", "localhost.localdomain", "localhost.localdomain.") || endswith_no_case(hostname, ".localhost") || endswith_no_case(hostname, ".localhost.") || endswith_no_case(hostname, ".localhost.localdomain") || endswith_no_case(hostname, ".localhost.localdomain."); } bool is_gateway_hostname(const char *hostname) { assert(hostname); /* This tries to identify the valid syntaxes for the our * synthetic "gateway" host. */ return strcaseeq(hostname, "_gateway") || strcaseeq(hostname, "_gateway.") #if ENABLE_COMPAT_GATEWAY_HOSTNAME || strcaseeq(hostname, "gateway") || strcaseeq(hostname, "gateway.") #endif ; } int sethostname_idempotent(const char *s) { char buf[HOST_NAME_MAX + 1] = {}; assert(s); if (gethostname(buf, sizeof(buf)) < 0) return -errno; if (streq(buf, s)) return 0; if (sethostname(s, strlen(s)) < 0) return -errno; return 1; } int shorten_overlong(const char *s, char **ret) { char *h, *p; /* Shorten an overlong name to HOST_NAME_MAX or to the first dot, * whatever comes earlier. */ assert(s); h = strdup(s); if (!h) return -ENOMEM; if (hostname_is_valid(h, false)) { *ret = h; return 0; } p = strchr(h, '.'); if (p) *p = 0; strshorten(h, HOST_NAME_MAX); if (!hostname_is_valid(h, false)) { free(h); return -EDOM; } *ret = h; return 1; } int read_etc_hostname_stream(FILE *f, char **ret) { int r; assert(f); assert(ret); for (;;) { _cleanup_free_ char *line = NULL; char *p; r = read_line(f, LONG_LINE_MAX, &line); if (r < 0) return r; if (r == 0) /* EOF without any hostname? the file is empty, let's treat that exactly like no file at all: ENOENT */ return -ENOENT; p = strstrip(line); /* File may have empty lines or comments, ignore them */ if (!IN_SET(*p, '\0', '#')) { char *copy; hostname_cleanup(p); /* normalize the hostname */ if (!hostname_is_valid(p, true)) /* check that the hostname we return is valid */ return -EBADMSG; copy = strdup(p); if (!copy) return -ENOMEM; *ret = copy; return 0; } } } int read_etc_hostname(const char *path, char **ret) { _cleanup_fclose_ FILE *f = NULL; assert(ret); if (!path) path = "/etc/hostname"; f = fopen(path, "re"); if (!f) return -errno; return read_etc_hostname_stream(f, ret); }