1
0
mirror of https://github.com/systemd/systemd.git synced 2026-01-25 00:33:29 +03:00
Files
systemd/src/basic/hostname-util.c
Valentin David 0dc39dffbd Use paths specified from environment variables for /etc configuration files
Some configuration files that need updates are directly under in /etc. To
update them atomically, we need write access to /etc. For Ubuntu Core this is
an issue as /etc is not writable. Only a selection of subdirectories can be
writable. The general solution is symlinks or bind mounts to writable places.
But for atomic writes in /etc, that does not work. So Ubuntu has had a patch
for that that did not age well.

Instead we would like to introduce some environment variables for alternate
paths.

 * SYSTEMD_ETC_HOSTNAME: /etc/hostname
 * SYSTEMD_ETC_MACHINE_INFO: /etc/machine-info
 * SYSTEMD_ETC_LOCALTIME: /etc/localtime
 * SYSTEMD_ETC_LOCALE_CONF: /etc/locale.conf
 * SYSTEMD_ETC_VCONSOLE_CONF: /etc/vconsole.conf
 * SYSTEMD_ETC_ADJTIME: /etc/adjtime

While it is for now expected that there is a symlink from the standard, we
still try to read them from that alternate path. This is important for
`/etc/localtime`, which is a symlink, so we cannot have an indirect symlink or
bind mount for it.

Since machine-id is typically written only once and not updated. This commit
does not cover it. An initrd can properly create it and bind mount it.
2025-06-23 15:32:11 +02:00

256 lines
7.7 KiB
C

/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <stdlib.h>
#include "alloc-util.h"
#include "env-file.h"
#include "hostname-util.h"
#include "log.h"
#include "os-util.h"
#include "string-util.h"
#include "strv.h"
#include "user-util.h"
char* get_default_hostname_raw(void) {
int r;
/* Returns the default hostname, and leaves any ??? in place. */
const char *e = secure_getenv("SYSTEMD_DEFAULT_HOSTNAME");
if (e) {
if (hostname_is_valid(e, VALID_HOSTNAME_QUESTION_MARK))
return strdup(e);
log_debug("Invalid hostname in $SYSTEMD_DEFAULT_HOSTNAME, ignoring: %s", e);
}
_cleanup_free_ char *f = NULL;
r = parse_os_release(NULL, "DEFAULT_HOSTNAME", &f);
if (r < 0)
log_debug_errno(r, "Failed to parse os-release, ignoring: %m");
else if (f) {
if (hostname_is_valid(f, VALID_HOSTNAME_QUESTION_MARK))
return TAKE_PTR(f);
log_debug("Invalid hostname in os-release, ignoring: %s", f);
}
return strdup(FALLBACK_HOSTNAME);
}
bool valid_ldh_char(char c) {
/* "LDH" → "Letters, digits, hyphens", as per RFC 5890, Section 2.3.1 */
return ascii_isalpha(c) ||
ascii_isdigit(c) ||
c == '-';
}
bool hostname_is_valid(const char *s, ValidHostnameFlags flags) {
unsigned n_dots = 0;
const char *p;
bool dot, hyphen;
/* 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()). A trailing dot is allowed if
* VALID_HOSTNAME_TRAILING_DOT flag is set 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(). Doesn't accept empty hostnames, hostnames with leading dots, and hostnames
* with multiple dots in a sequence. Doesn't allow hyphens at the beginning or end of label. */
if (isempty(s))
return false;
if (streq(s, ".host")) /* Used by the container logic to denote the "root container" */
return FLAGS_SET(flags, VALID_HOSTNAME_DOT_HOST);
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) && (*p != '?' || !FLAGS_SET(flags, VALID_HOSTNAME_QUESTION_MARK)))
return false;
dot = false;
hyphen = false;
}
if (dot && (n_dots < 2 || !FLAGS_SET(flags, VALID_HOSTNAME_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) || *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.");
}
const char* etc_hostname(void) {
static const char *cached = NULL;
if (!cached)
cached = secure_getenv("SYSTEMD_ETC_HOSTNAME") ?: "/etc/hostname";
return cached;
}
const char* etc_machine_info(void) {
static const char *cached = NULL;
if (!cached)
cached = secure_getenv("SYSTEMD_ETC_MACHINE_INFO") ?: "/etc/machine-info";
return cached;
}
int get_pretty_hostname(char **ret) {
_cleanup_free_ char *n = NULL;
int r;
assert(ret);
r = parse_env_file(NULL, etc_machine_info(), "PRETTY_HOSTNAME", &n);
if (r < 0)
return r;
if (isempty(n))
return -ENXIO;
*ret = TAKE_PTR(n);
return 0;
}
int split_user_at_host(const char *s, char **ret_user, char **ret_host) {
_cleanup_free_ char *u = NULL, *h = NULL;
/* Splits a user@host expression (one of those we accept on --machine= and similar). Returns NULL in
* each of the two return parameters if that part was left empty. */
assert(s);
const char *rhs = strchr(s, '@');
if (rhs) {
if (ret_user && rhs > s) {
u = strndup(s, rhs - s);
if (!u)
return -ENOMEM;
}
if (ret_host && rhs[1] != 0) {
h = strdup(rhs + 1);
if (!h)
return -ENOMEM;
}
} else {
if (isempty(s))
return -EINVAL;
if (ret_host) {
h = strdup(s);
if (!h)
return -ENOMEM;
}
}
if (ret_user)
*ret_user = TAKE_PTR(u);
if (ret_host)
*ret_host = TAKE_PTR(h);
return !!rhs; /* return > 0 if '@' was specified, 0 otherwise */
}
int machine_spec_valid(const char *s) {
_cleanup_free_ char *u = NULL, *h = NULL;
int r;
assert(s);
r = split_user_at_host(s, &u, &h);
if (r == -EINVAL)
return false;
if (r < 0)
return r;
if (u && !valid_user_group_name(u, VALID_USER_RELAX | VALID_USER_ALLOW_NUMERIC))
return false;
if (h && !hostname_is_valid(h, VALID_HOSTNAME_DOT_HOST))
return false;
return true;
}