diff --git a/TODO b/TODO
index 62c7f8b30d7..c238460222a 100644
--- a/TODO
+++ b/TODO
@@ -1454,8 +1454,6 @@ Features:
comes from, but we can still derive that from the stdin socket its output
came from. We apparently don't do that right now.
-* add ability to set hostname with suffix derived from machine id at boot
-
* add PR_SET_DUMPABLE service setting
* homed/userdb: maybe define a "companion" dir for home directories where apps
diff --git a/man/hostname.xml b/man/hostname.xml
index 746de21cd12..76acb8d7a5c 100644
--- a/man/hostname.xml
+++ b/man/hostname.xml
@@ -43,6 +43,13 @@
attempt to make the name valid, but obviously it is recommended to use a valid name and not rely on this
filtering.
+ If the question mark character ? appears in
+ the hostname, it is automatically substituted by a hexadecimal character derived from the
+ machine-id5 when
+ applied, securely and deterministically by cryptographic hashing. Example:
+ foobar-????-???? will automatically expand to foobar-92a9-061c or
+ similar, depending on the local machine ID.
+
You may use
hostnamectl1 to change
the value of this file during runtime from the command line. Use
diff --git a/man/hostnamectl.xml b/man/hostnamectl.xml
index 70a6d295f6b..07cb0bc005a 100644
--- a/man/hostnamectl.xml
+++ b/man/hostnamectl.xml
@@ -88,6 +88,8 @@
name labels), or a sequence of such labels separated by single dots that forms a valid DNS FQDN. The
hostname must be at most 64 characters, which is a Linux limitation (DNS allows longer names).
+
+
diff --git a/man/os-release.xml b/man/os-release.xml
index 548eb47a4c0..54978edd43c 100644
--- a/man/os-release.xml
+++ b/man/os-release.xml
@@ -538,6 +538,8 @@
that forms a valid DNS FQDN. The hostname must be at most 64 characters, which is a Linux
limitation (DNS allows longer names).
+
+
See org.freedesktop.hostname15
for a description of how
systemd-hostnamed.service8
diff --git a/src/basic/hostname-util.c b/src/basic/hostname-util.c
index e743033b1ea..7159473b582 100644
--- a/src/basic/hostname-util.c
+++ b/src/basic/hostname-util.c
@@ -14,13 +14,16 @@
#include "string-util.h"
#include "strv.h"
-char* get_default_hostname(void) {
+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, 0))
+ if (hostname_is_valid(e, VALID_HOSTNAME_QUESTION_MARK))
return strdup(e);
+
log_debug("Invalid hostname in $SYSTEMD_DEFAULT_HOSTNAME, ignoring: %s", e);
}
@@ -29,49 +32,15 @@ char* get_default_hostname(void) {
if (r < 0)
log_debug_errno(r, "Failed to parse os-release, ignoring: %m");
else if (f) {
- if (hostname_is_valid(f, 0))
+ 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);
}
-int gethostname_full(GetHostnameFlags flags, char **ret) {
- _cleanup_free_ char *buf = NULL, *fallback = NULL;
- struct utsname u;
- const char *s;
-
- assert(ret);
-
- assert_se(uname(&u) >= 0);
-
- s = u.nodename;
- if (isempty(s) || streq(s, "(none)") ||
- (!FLAGS_SET(flags, GET_HOSTNAME_ALLOW_LOCALHOST) && is_localhost(s)) ||
- (FLAGS_SET(flags, GET_HOSTNAME_SHORT) && s[0] == '.')) {
- if (!FLAGS_SET(flags, GET_HOSTNAME_FALLBACK_DEFAULT))
- return -ENXIO;
-
- s = fallback = get_default_hostname();
- if (!s)
- return -ENOMEM;
-
- if (FLAGS_SET(flags, GET_HOSTNAME_SHORT) && s[0] == '.')
- return -ENXIO;
- }
-
- if (FLAGS_SET(flags, GET_HOSTNAME_SHORT))
- buf = strdupcspn(s, ".");
- else
- buf = strdup(s);
- if (!buf)
- return -ENOMEM;
-
- *ret = TAKE_PTR(buf);
- return 0;
-}
-
bool valid_ldh_char(char c) {
/* "LDH" → "Letters, digits, hyphens", as per RFC 5890, Section 2.3.1 */
@@ -116,7 +85,7 @@ bool hostname_is_valid(const char *s, ValidHostnameFlags flags) {
hyphen = true;
} else {
- if (!valid_ldh_char(*p))
+ if (!valid_ldh_char(*p) && (*p != '?' || !FLAGS_SET(flags, VALID_HOSTNAME_QUESTION_MARK)))
return false;
dot = false;
@@ -158,7 +127,7 @@ char* hostname_cleanup(char *s) {
dot = false;
hyphen = true;
- } else if (valid_ldh_char(*p)) {
+ } else if (valid_ldh_char(*p) || *p == '?') {
*(d++) = *p;
dot = false;
hyphen = false;
diff --git a/src/basic/hostname-util.h b/src/basic/hostname-util.h
index bcac3d9fb06..4c5abe760f0 100644
--- a/src/basic/hostname-util.h
+++ b/src/basic/hostname-util.h
@@ -7,42 +7,14 @@
#include "macro.h"
#include "strv.h"
-typedef enum GetHostnameFlags {
- GET_HOSTNAME_ALLOW_LOCALHOST = 1 << 0, /* accepts "localhost" or friends. */
- GET_HOSTNAME_FALLBACK_DEFAULT = 1 << 1, /* use default hostname if no hostname is set. */
- GET_HOSTNAME_SHORT = 1 << 2, /* kills the FQDN part if present. */
-} GetHostnameFlags;
-
-int gethostname_full(GetHostnameFlags flags, char **ret);
-static inline int gethostname_strict(char **ret) {
- return gethostname_full(0, ret);
-}
-
-static inline char* gethostname_malloc(void) {
- char *s;
-
- if (gethostname_full(GET_HOSTNAME_ALLOW_LOCALHOST | GET_HOSTNAME_FALLBACK_DEFAULT, &s) < 0)
- return NULL;
-
- return s;
-}
-
-static inline char* gethostname_short_malloc(void) {
- char *s;
-
- if (gethostname_full(GET_HOSTNAME_ALLOW_LOCALHOST | GET_HOSTNAME_FALLBACK_DEFAULT | GET_HOSTNAME_SHORT, &s) < 0)
- return NULL;
-
- return s;
-}
-
-char* get_default_hostname(void);
+char* get_default_hostname_raw(void);
bool valid_ldh_char(char c) _const_;
typedef enum ValidHostnameFlags {
- VALID_HOSTNAME_TRAILING_DOT = 1 << 0, /* Accept trailing dot on multi-label names */
- VALID_HOSTNAME_DOT_HOST = 1 << 1, /* Accept ".host" as valid hostname */
+ VALID_HOSTNAME_TRAILING_DOT = 1 << 0, /* Accept trailing dot on multi-label names */
+ VALID_HOSTNAME_DOT_HOST = 1 << 1, /* Accept ".host" as valid hostname */
+ VALID_HOSTNAME_QUESTION_MARK = 1 << 2, /* Accept "?" as place holder for hashed machine ID value */
} ValidHostnameFlags;
bool hostname_is_valid(const char *s, ValidHostnameFlags flags) _pure_;
diff --git a/src/core/main.c b/src/core/main.c
index 0368ec891fc..ee4b2d6bafb 100644
--- a/src/core/main.c
+++ b/src/core/main.c
@@ -2453,12 +2453,11 @@ static int initialize_runtime(
(void) import_credentials();
(void) os_release_status();
- (void) hostname_setup(/* really = */ true);
(void) machine_id_setup(/* root = */ NULL, arg_machine_id,
(first_boot ? MACHINE_ID_SETUP_FORCE_TRANSIENT : 0) |
(arg_machine_id_from_firmware ? MACHINE_ID_SETUP_FORCE_FIRMWARE : 0),
/* ret_machine_id = */ NULL);
-
+ (void) hostname_setup(/* really = */ true);
(void) loopback_setup();
bump_unix_max_dgram_qlen();
diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c
index 9ef8d89560e..cd72f543969 100644
--- a/src/firstboot/firstboot.c
+++ b/src/firstboot/firstboot.c
@@ -1460,7 +1460,7 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ARG_HOSTNAME:
- if (!hostname_is_valid(optarg, VALID_HOSTNAME_TRAILING_DOT))
+ if (!hostname_is_valid(optarg, VALID_HOSTNAME_TRAILING_DOT|VALID_HOSTNAME_QUESTION_MARK))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Host name %s is not valid.", optarg);
diff --git a/src/fuzz/fuzz-hostname-setup.c b/src/fuzz/fuzz-hostname-setup.c
index 4895631b67b..6ca0dc6fa53 100644
--- a/src/fuzz/fuzz-hostname-setup.c
+++ b/src/fuzz/fuzz-hostname-setup.c
@@ -14,7 +14,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
fuzz_setup_logging();
- (void) read_etc_hostname_stream(f, &ret);
+ (void) read_etc_hostname_stream(f, /* substitute_wildcards= */ true, &ret);
return 0;
}
diff --git a/src/hostname/hostnamectl.c b/src/hostname/hostnamectl.c
index bf03f177d37..144e3550a30 100644
--- a/src/hostname/hostnamectl.c
+++ b/src/hostname/hostnamectl.c
@@ -598,7 +598,7 @@ static int set_hostname(int argc, char **argv, void *userdata) {
/* If the passed hostname is already valid, then assume the user doesn't know anything about pretty
* hostnames, so let's unset the pretty hostname, and just set the passed hostname as static/dynamic
* hostname. */
- if (implicit && hostname_is_valid(hostname, VALID_HOSTNAME_TRAILING_DOT))
+ if (implicit && hostname_is_valid(hostname, VALID_HOSTNAME_TRAILING_DOT|VALID_HOSTNAME_QUESTION_MARK))
p = ""; /* No pretty hostname (as it is redundant), just a static one */
else
p = hostname; /* Use the passed name as pretty hostname */
diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c
index 110f0a7400c..fb80e1280a6 100644
--- a/src/hostname/hostnamed.c
+++ b/src/hostname/hostnamed.c
@@ -52,6 +52,7 @@
typedef enum {
/* Read from /etc/hostname */
PROP_STATIC_HOSTNAME,
+ PROP_STATIC_HOSTNAME_SUBSTITUTED_WILDCARDS,
/* Read from /etc/machine-info */
PROP_PRETTY_HOSTNAME,
@@ -125,11 +126,25 @@ static void context_read_etc_hostname(Context *c) {
stat_inode_unmodified(&c->etc_hostname_stat, ¤t_stat))
return;
- context_reset(c, UINT64_C(1) << PROP_STATIC_HOSTNAME);
+ context_reset(c,
+ (UINT64_C(1) << PROP_STATIC_HOSTNAME) |
+ (UINT64_C(1) << PROP_STATIC_HOSTNAME_SUBSTITUTED_WILDCARDS));
- r = read_etc_hostname(NULL, &c->data[PROP_STATIC_HOSTNAME]);
- if (r < 0 && r != -ENOENT)
- log_warning_errno(r, "Failed to read /etc/hostname, ignoring: %m");
+ r = read_etc_hostname(/* path= */ NULL, /* substitute_wildcards= */ false, &c->data[PROP_STATIC_HOSTNAME]);
+ if (r < 0) {
+ if (r != -ENOENT)
+ log_warning_errno(r, "Failed to read /etc/hostname, ignoring: %m");
+ } else {
+ _cleanup_free_ char *substituted = strdup(c->data[PROP_STATIC_HOSTNAME]);
+ if (!substituted)
+ return (void) log_oom();
+
+ r = hostname_substitute_wildcards(substituted);
+ if (r < 0)
+ log_warning_errno(r, "Failed to substitute wildcards in /etc/hostname, ignoring: %m");
+ else
+ c->data[PROP_STATIC_HOSTNAME_SUBSTITUTED_WILDCARDS] = TAKE_PTR(substituted);
+ }
c->etc_hostname_stat = current_stat;
}
@@ -678,8 +693,8 @@ static int context_update_kernel_hostname(
assert(c);
/* /etc/hostname has the highest preference ... */
- if (c->data[PROP_STATIC_HOSTNAME]) {
- hn = c->data[PROP_STATIC_HOSTNAME];
+ if (c->data[PROP_STATIC_HOSTNAME_SUBSTITUTED_WILDCARDS]) {
+ hn = c->data[PROP_STATIC_HOSTNAME_SUBSTITUTED_WILDCARDS];
hns = HOSTNAME_STATIC;
/* ... the transient hostname, (ie: DHCP) comes next ... */
@@ -946,7 +961,7 @@ static int property_get_static_hostname(
context_read_etc_hostname(c);
- return sd_bus_message_append(reply, "s", c->data[PROP_STATIC_HOSTNAME]);
+ return sd_bus_message_append(reply, "s", c->data[PROP_STATIC_HOSTNAME_SUBSTITUTED_WILDCARDS]);
}
static int property_get_default_hostname(
@@ -978,7 +993,7 @@ static void context_determine_hostname_source(Context *c) {
(void) gethostname_full(GET_HOSTNAME_ALLOW_LOCALHOST, &hostname);
- if (streq_ptr(hostname, c->data[PROP_STATIC_HOSTNAME]))
+ if (streq_ptr(hostname, c->data[PROP_STATIC_HOSTNAME_SUBSTITUTED_WILDCARDS]))
c->hostname_source = HOSTNAME_STATIC;
else {
_cleanup_free_ char *fallback = NULL;
@@ -1201,6 +1216,31 @@ static int property_get_vsock_cid(
return sd_bus_message_append(reply, "u", (uint32_t) local_cid);
}
+static int validate_and_substitute_hostname(const char *name, char **ret_substituted, sd_bus_error *error) {
+ int r;
+
+ assert(ret_substituted);
+
+ if (!name) {
+ *ret_substituted = NULL;
+ return 0;
+ }
+
+ _cleanup_free_ char *substituted = strdup(name);
+ if (!substituted)
+ return log_oom();
+
+ r = hostname_substitute_wildcards(substituted);
+ if (r < 0)
+ return log_error_errno(r, "Failed to substitute wildcards in hotname: %m");
+
+ if (!hostname_is_valid(substituted, 0))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", name);
+
+ *ret_substituted = TAKE_PTR(substituted);
+ return 1;
+}
+
static int method_set_hostname(sd_bus_message *m, void *userdata, sd_bus_error *error) {
Context *c = ASSERT_PTR(userdata);
const char *name;
@@ -1217,10 +1257,12 @@ static int method_set_hostname(sd_bus_message *m, void *userdata, sd_bus_error *
/* We always go through with the procedure below without comparing to the current hostname, because
* we might want to adjust hostname source information even if the actual hostname is unchanged. */
- if (name && !hostname_is_valid(name, 0))
- return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", name);
+ _cleanup_free_ char *substituted = NULL;
+ r = validate_and_substitute_hostname(name, &substituted, error);
+ if (r < 0)
+ return r;
- context_read_etc_hostname(c);
+ name = substituted;
r = bus_verify_polkit_async_full(
m,
@@ -1235,6 +1277,8 @@ static int method_set_hostname(sd_bus_message *m, void *userdata, sd_bus_error *
if (r == 0)
return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
+ context_read_etc_hostname(c);
+
r = context_update_kernel_hostname(c, name);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to set hostname: %m");
@@ -1249,8 +1293,7 @@ static int method_set_hostname(sd_bus_message *m, void *userdata, sd_bus_error *
static int method_set_static_hostname(sd_bus_message *m, void *userdata, sd_bus_error *error) {
Context *c = ASSERT_PTR(userdata);
const char *name;
- int interactive;
- int r;
+ int interactive, r;
assert(m);
@@ -1265,8 +1308,10 @@ static int method_set_static_hostname(sd_bus_message *m, void *userdata, sd_bus_
if (streq_ptr(name, c->data[PROP_STATIC_HOSTNAME]))
return sd_bus_reply_method_return(m, NULL);
- if (name && !hostname_is_valid(name, 0))
- return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid static hostname '%s'", name);
+ _cleanup_free_ char *substituted = NULL;
+ r = validate_and_substitute_hostname(name, &substituted, error);
+ if (r < 0)
+ return r;
r = bus_verify_polkit_async_full(
m,
@@ -1285,6 +1330,8 @@ static int method_set_static_hostname(sd_bus_message *m, void *userdata, sd_bus_
if (r < 0)
return r;
+ free_and_replace(c->data[PROP_STATIC_HOSTNAME_SUBSTITUTED_WILDCARDS], substituted);
+
r = context_write_data_static_hostname(c);
if (r < 0) {
log_error_errno(r, "Failed to write static hostname: %m");
@@ -1295,7 +1342,7 @@ static int method_set_static_hostname(sd_bus_message *m, void *userdata, sd_bus_
return sd_bus_error_set_errnof(error, r, "Failed to set static hostname: %m");
}
- r = context_update_kernel_hostname(c, NULL);
+ r = context_update_kernel_hostname(c, /* transient_hostname= */ NULL);
if (r < 0) {
log_error_errno(r, "Failed to set hostname: %m");
return sd_bus_error_set_errnof(error, r, "Failed to set hostname: %m");
@@ -1505,20 +1552,14 @@ static int build_describe_response(Context *c, bool privileged, sd_json_variant
context_read_os_release(c);
context_determine_hostname_source(c);
- r = gethostname_strict(&hn);
- if (r < 0) {
- if (r != -ENXIO)
- return log_error_errno(r, "Failed to read local host name: %m");
-
- hn = get_default_hostname();
- if (!hn)
- return log_oom();
- }
-
dhn = get_default_hostname();
if (!dhn)
return log_oom();
+ r = gethostname_strict(&hn);
+ if (r < 0 && r != -ENXIO)
+ return log_error_errno(r, "Failed to read local host name: %m");
+
if (isempty(c->data[PROP_ICON_NAME]))
in = context_fallback_icon_name(c);
@@ -1559,8 +1600,8 @@ static int build_describe_response(Context *c, bool privileged, sd_json_variant
r = sd_json_buildo(
&v,
- SD_JSON_BUILD_PAIR_STRING("Hostname", hn),
- SD_JSON_BUILD_PAIR_STRING("StaticHostname", c->data[PROP_STATIC_HOSTNAME]),
+ SD_JSON_BUILD_PAIR_STRING("Hostname", hn ?: dhn),
+ SD_JSON_BUILD_PAIR_STRING("StaticHostname", c->data[PROP_STATIC_HOSTNAME_SUBSTITUTED_WILDCARDS]),
SD_JSON_BUILD_PAIR_STRING("PrettyHostname", c->data[PROP_PRETTY_HOSTNAME]),
SD_JSON_BUILD_PAIR_STRING("DefaultHostname", dhn),
SD_JSON_BUILD_PAIR_STRING("HostnameSource", hostname_source_to_string(c->hostname_source)),
diff --git a/src/journal-remote/journal-gatewayd.c b/src/journal-remote/journal-gatewayd.c
index ef20eb2ff83..8e75f8166e2 100644
--- a/src/journal-remote/journal-gatewayd.c
+++ b/src/journal-remote/journal-gatewayd.c
@@ -21,6 +21,7 @@
#include "fd-util.h"
#include "fileio.h"
#include "glob-util.h"
+#include "hostname-setup.h"
#include "hostname-util.h"
#include "journal-internal.h"
#include "journal-remote.h"
diff --git a/src/journal/journalctl-authenticate.c b/src/journal/journalctl-authenticate.c
index 7aaa340cd3c..18b412bbc70 100644
--- a/src/journal/journalctl-authenticate.c
+++ b/src/journal/journalctl-authenticate.c
@@ -8,6 +8,7 @@
#include "fd-util.h"
#include "fs-util.h"
#include "fsprg.h"
+#include "hostname-setup.h"
#include "hostname-util.h"
#include "io-util.h"
#include "journal-authenticate.h"
diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c
index 16fb88324ff..4faaae5d771 100644
--- a/src/journal/journald-server.c
+++ b/src/journal/journald-server.c
@@ -27,6 +27,7 @@
#include "format-util.h"
#include "fs-util.h"
#include "hashmap.h"
+#include "hostname-setup.h"
#include "hostname-util.h"
#include "id128-util.h"
#include "initrd-util.h"
diff --git a/src/libsystemd-network/sd-lldp-tx.c b/src/libsystemd-network/sd-lldp-tx.c
index 01c476ecde4..38a619168ee 100644
--- a/src/libsystemd-network/sd-lldp-tx.c
+++ b/src/libsystemd-network/sd-lldp-tx.c
@@ -11,6 +11,7 @@
#include "alloc-util.h"
#include "ether-addr-util.h"
#include "fd-util.h"
+#include "hostname-setup.h"
#include "hostname-util.h"
#include "network-common.h"
#include "random-util.h"
diff --git a/src/nss-myhostname/nss-myhostname.c b/src/nss-myhostname/nss-myhostname.c
index ed417306f02..a19a362896d 100644
--- a/src/nss-myhostname/nss-myhostname.c
+++ b/src/nss-myhostname/nss-myhostname.c
@@ -8,6 +8,7 @@
#include "alloc-util.h"
#include "errno-util.h"
+#include "hostname-setup.h"
#include "hostname-util.h"
#include "local-addresses.h"
#include "macro.h"
diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c
index 40e0d74e5b0..ed65c24e922 100644
--- a/src/resolve/resolved-manager.c
+++ b/src/resolve/resolved-manager.c
@@ -18,6 +18,7 @@
#include "event-util.h"
#include "fd-util.h"
#include "fileio.h"
+#include "hostname-setup.h"
#include "hostname-util.h"
#include "idn-util.h"
#include "io-util.h"
diff --git a/src/resolve/resolved-util.c b/src/resolve/resolved-util.c
index adcd35d6bee..8c604c96f6e 100644
--- a/src/resolve/resolved-util.c
+++ b/src/resolve/resolved-util.c
@@ -2,6 +2,7 @@
#include "dns-def.h"
#include "dns-domain.h"
+#include "hostname-setup.h"
#include "hostname-util.h"
#include "idn-util.h"
#include "resolved-util.h"
diff --git a/src/run/run.c b/src/run/run.c
index ae4b2b88336..cb82bfa9721 100644
--- a/src/run/run.c
+++ b/src/run/run.c
@@ -30,6 +30,7 @@
#include "fd-util.h"
#include "format-util.h"
#include "fs-util.h"
+#include "hostname-setup.h"
#include "hostname-util.h"
#include "main-func.h"
#include "osc-context.h"
diff --git a/src/shared/condition.c b/src/shared/condition.c
index ebfd1e1aabe..d57f46e8a0d 100644
--- a/src/shared/condition.c
+++ b/src/shared/condition.c
@@ -38,6 +38,7 @@
#include "fileio.h"
#include "fs-util.h"
#include "glob-util.h"
+#include "hostname-setup.h"
#include "hostname-util.h"
#include "ima-util.h"
#include "id128-util.h"
diff --git a/src/shared/discover-image.c b/src/shared/discover-image.c
index e99a0d6cae8..f9a1b2ffaa5 100644
--- a/src/shared/discover-image.c
+++ b/src/shared/discover-image.c
@@ -1654,7 +1654,7 @@ int image_read_metadata(Image *i, const ImagePolicy *image_policy) {
if (r < 0 && r != -ENOENT)
log_debug_errno(r, "Failed to chase /etc/hostname in image %s: %m", i->name);
else if (r >= 0) {
- r = read_etc_hostname(path, &hostname);
+ r = read_etc_hostname(path, /* substitute_wildcards= */ false, &hostname);
if (r < 0)
log_debug_errno(r, "Failed to read /etc/hostname of image %s: %m", i->name);
}
diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c
index ef3a456ea7f..91f9d0c9ccf 100644
--- a/src/shared/dissect-image.c
+++ b/src/shared/dissect-image.c
@@ -3692,7 +3692,7 @@ int dissected_image_acquire_metadata(
switch (k) {
case META_HOSTNAME:
- r = read_etc_hostname_stream(f, &hostname);
+ r = read_etc_hostname_stream(f, /* substitute_wildcards= */ false, &hostname);
if (r < 0)
log_debug_errno(r, "Failed to read /etc/hostname of image: %m");
diff --git a/src/shared/hostname-setup.c b/src/shared/hostname-setup.c
index 6cfd4b54bf6..af89f92bdc4 100644
--- a/src/shared/hostname-setup.c
+++ b/src/shared/hostname-setup.c
@@ -13,12 +13,14 @@
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
+#include "hexdecoct.h"
#include "hostname-setup.h"
#include "hostname-util.h"
#include "initrd-util.h"
#include "log.h"
#include "macro.h"
#include "proc-cmdline.h"
+#include "siphash24.h"
#include "string-table.h"
#include "string-util.h"
@@ -95,7 +97,7 @@ static int acquire_hostname_from_credential(char **ret) {
return 0;
}
-int read_etc_hostname_stream(FILE *f, char **ret) {
+int read_etc_hostname_stream(FILE *f, bool substitute_wildcards, char **ret) {
int r;
assert(f);
@@ -114,9 +116,19 @@ int read_etc_hostname_stream(FILE *f, char **ret) {
if (IN_SET(line[0], '\0', '#'))
continue;
+ if (substitute_wildcards) {
+ r = hostname_substitute_wildcards(line);
+ if (r < 0)
+ return r;
+ }
+
hostname_cleanup(line); /* normalize the hostname */
- if (!hostname_is_valid(line, VALID_HOSTNAME_TRAILING_DOT)) /* check that the hostname we return is valid */
+ /* check that the hostname we return is valid */
+ if (!hostname_is_valid(
+ line,
+ VALID_HOSTNAME_TRAILING_DOT|
+ (substitute_wildcards ? 0 : VALID_HOSTNAME_QUESTION_MARK)))
return -EBADMSG;
*ret = TAKE_PTR(line);
@@ -124,7 +136,7 @@ int read_etc_hostname_stream(FILE *f, char **ret) {
}
}
-int read_etc_hostname(const char *path, char **ret) {
+int read_etc_hostname(const char *path, bool substitute_wildcards, char **ret) {
_cleanup_fclose_ FILE *f = NULL;
assert(ret);
@@ -136,12 +148,14 @@ int read_etc_hostname(const char *path, char **ret) {
if (!f)
return -errno;
- return read_etc_hostname_stream(f, ret);
+ return read_etc_hostname_stream(f, substitute_wildcards, ret);
}
void hostname_update_source_hint(const char *hostname, HostnameSource source) {
int r;
+ assert(hostname);
+
/* Why save the value and not just create a flag file? This way we will
* notice if somebody sets the hostname directly (not going through hostnamed).
*/
@@ -152,7 +166,7 @@ void hostname_update_source_hint(const char *hostname, HostnameSource source) {
if (r < 0)
log_warning_errno(r, "Failed to create \"/run/systemd/default-hostname\", ignoring: %m");
} else
- unlink_or_warn("/run/systemd/default-hostname");
+ (void) unlink_or_warn("/run/systemd/default-hostname");
}
int hostname_setup(bool really) {
@@ -174,7 +188,7 @@ int hostname_setup(bool really) {
}
if (!hn) {
- r = read_etc_hostname(NULL, &hn);
+ r = read_etc_hostname(/* path= */ NULL, /* substitute_wildcards= */ true, &hn);
if (r == -ENOENT)
enoent = true;
else if (r < 0)
@@ -237,3 +251,99 @@ static const char* const hostname_source_table[] = {
};
DEFINE_STRING_TABLE_LOOKUP(hostname_source, HostnameSource);
+
+int hostname_substitute_wildcards(char *name) {
+ static const sd_id128_t key = SD_ID128_MAKE(98,10,ad,df,8d,7d,4f,b5,89,1b,4b,56,ac,c2,26,8f);
+ sd_id128_t mid = SD_ID128_NULL;
+ size_t left_bits = 0, counter = 0;
+ uint64_t h = 0;
+ int r;
+
+ assert(name);
+
+ /* Replaces every occurrence of '?' in the specified string with a nibble hashed from
+ * /etc/machine-id. This is supposed to be used on /etc/hostname files that want to automatically
+ * configure a hostname derived from the machine ID in some form.
+ *
+ * Note that this does not directly use the machine ID, because that's not necessarily supposed to be
+ * public information to be broadcast on the network, while the hostname certainly is. */
+
+ for (char *n = name; *n; n++) {
+ if (*n != '?')
+ continue;
+
+ if (left_bits <= 0) {
+ if (sd_id128_is_null(mid)) {
+ r = sd_id128_get_machine(&mid);
+ if (r < 0)
+ return r;
+ }
+
+ struct siphash state;
+ siphash24_init(&state, key.bytes);
+ siphash24_compress(&mid, sizeof(mid), &state);
+ siphash24_compress(&counter, sizeof(counter), &state); /* counter mode */
+ h = siphash24_finalize(&state);
+ left_bits = sizeof(h) * 8;
+ counter++;
+ }
+
+ assert(left_bits >= 4);
+ *n = hexchar(h & 0xf);
+ h >>= 4;
+ left_bits -= 4;
+ }
+
+ return 0;
+}
+
+char* get_default_hostname(void) {
+ int r;
+
+ _cleanup_free_ char *h = get_default_hostname_raw();
+ if (!h)
+ return NULL;
+
+ r = hostname_substitute_wildcards(h);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to substitute wildcards in hostname, falling back to built-in name: %m");
+ return strdup(FALLBACK_HOSTNAME);
+ }
+
+ return TAKE_PTR(h);
+}
+
+int gethostname_full(GetHostnameFlags flags, char **ret) {
+ _cleanup_free_ char *buf = NULL, *fallback = NULL;
+ struct utsname u;
+ const char *s;
+
+ assert(ret);
+
+ assert_se(uname(&u) >= 0);
+
+ s = u.nodename;
+ if (isempty(s) || streq(s, "(none)") ||
+ (!FLAGS_SET(flags, GET_HOSTNAME_ALLOW_LOCALHOST) && is_localhost(s)) ||
+ (FLAGS_SET(flags, GET_HOSTNAME_SHORT) && s[0] == '.')) {
+ if (!FLAGS_SET(flags, GET_HOSTNAME_FALLBACK_DEFAULT))
+ return -ENXIO;
+
+ s = fallback = get_default_hostname();
+ if (!s)
+ return -ENOMEM;
+
+ if (FLAGS_SET(flags, GET_HOSTNAME_SHORT) && s[0] == '.')
+ return -ENXIO;
+ }
+
+ if (FLAGS_SET(flags, GET_HOSTNAME_SHORT))
+ buf = strdupcspn(s, ".");
+ else
+ buf = strdup(s);
+ if (!buf)
+ return -ENOMEM;
+
+ *ret = TAKE_PTR(buf);
+ return 0;
+}
diff --git a/src/shared/hostname-setup.h b/src/shared/hostname-setup.h
index 6def36c350e..bc602acbfc4 100644
--- a/src/shared/hostname-setup.h
+++ b/src/shared/hostname-setup.h
@@ -18,8 +18,42 @@ int sethostname_idempotent(const char *s);
int shorten_overlong(const char *s, char **ret);
-int read_etc_hostname_stream(FILE *f, char **ret);
-int read_etc_hostname(const char *path, char **ret);
+int read_etc_hostname_stream(FILE *f, bool substitute_wildcards, char **ret);
+int read_etc_hostname(const char *path, bool substitue_wildcards, char **ret);
void hostname_update_source_hint(const char *hostname, HostnameSource source);
int hostname_setup(bool really);
+
+int hostname_substitute_wildcards(char *name);
+
+char* get_default_hostname(void);
+
+typedef enum GetHostnameFlags {
+ GET_HOSTNAME_ALLOW_LOCALHOST = 1 << 0, /* accepts "localhost" or friends. */
+ GET_HOSTNAME_FALLBACK_DEFAULT = 1 << 1, /* use default hostname if no hostname is set. */
+ GET_HOSTNAME_SHORT = 1 << 2, /* kills the FQDN part if present. */
+} GetHostnameFlags;
+
+int gethostname_full(GetHostnameFlags flags, char **ret);
+
+static inline int gethostname_strict(char **ret) {
+ return gethostname_full(0, ret);
+}
+
+static inline char* gethostname_malloc(void) {
+ char *s;
+
+ if (gethostname_full(GET_HOSTNAME_ALLOW_LOCALHOST | GET_HOSTNAME_FALLBACK_DEFAULT, &s) < 0)
+ return NULL;
+
+ return s;
+}
+
+static inline char* gethostname_short_malloc(void) {
+ char *s;
+
+ if (gethostname_full(GET_HOSTNAME_ALLOW_LOCALHOST | GET_HOSTNAME_FALLBACK_DEFAULT | GET_HOSTNAME_SHORT, &s) < 0)
+ return NULL;
+
+ return s;
+}
diff --git a/src/shared/osc-context.c b/src/shared/osc-context.c
index 52534ba63d7..9d5e875fc3d 100644
--- a/src/shared/osc-context.c
+++ b/src/shared/osc-context.c
@@ -3,12 +3,13 @@
#include
#include "escape.h"
-#include "hostname-util.h"
+#include "hostname-setup.h"
#include "id128-util.h"
#include "osc-context.h"
#include "pidfd-util.h"
#include "process-util.h"
#include "string-util.h"
+#include "strv.h"
#include "terminal-util.h"
#include "user-util.h"
diff --git a/src/shared/specifier.c b/src/shared/specifier.c
index f6739f2c662..950b848d40c 100644
--- a/src/shared/specifier.c
+++ b/src/shared/specifier.c
@@ -14,6 +14,7 @@
#include "fd-util.h"
#include "format-util.h"
#include "fs-util.h"
+#include "hostname-setup.h"
#include "hostname-util.h"
#include "id128-util.h"
#include "macro.h"
diff --git a/src/shared/user-record.c b/src/shared/user-record.c
index 0d2c261a097..2744d146f7f 100644
--- a/src/shared/user-record.c
+++ b/src/shared/user-record.c
@@ -10,6 +10,7 @@
#include "fs-util.h"
#include "glyph-util.h"
#include "hexdecoct.h"
+#include "hostname-setup.h"
#include "hostname-util.h"
#include "json-util.h"
#include "locale-util.h"
diff --git a/src/shared/wall.c b/src/shared/wall.c
index b28c04cd8b3..11048f39aad 100644
--- a/src/shared/wall.c
+++ b/src/shared/wall.c
@@ -8,7 +8,7 @@
#include "errno-util.h"
#include "fd-util.h"
-#include "hostname-util.h"
+#include "hostname-setup.h"
#include "io-util.h"
#include "path-util.h"
#include "string-util.h"
diff --git a/src/systemctl/systemctl-list-machines.c b/src/systemctl/systemctl-list-machines.c
index 1fffceb38e0..1926b386911 100644
--- a/src/systemctl/systemctl-list-machines.c
+++ b/src/systemctl/systemctl-list-machines.c
@@ -6,6 +6,7 @@
#include "ansi-color.h"
#include "bus-map-properties.h"
+#include "hostname-setup.h"
#include "hostname-util.h"
#include "locale-util.h"
#include "memory-util.h"
diff --git a/src/systemctl/systemctl-show.c b/src/systemctl/systemctl-show.c
index fe35553e30c..bd61412cca6 100644
--- a/src/systemctl/systemctl-show.c
+++ b/src/systemctl/systemctl-show.c
@@ -17,6 +17,7 @@
#include "format-util.h"
#include "hexdecoct.h"
#include "hostname-util.h"
+#include "hostname-setup.h"
#include "in-addr-util.h"
#include "ip-protocol-list.h"
#include "journal-file.h"
diff --git a/src/test/test-condition.c b/src/test/test-condition.c
index 7e983213500..ef0a98a29fb 100644
--- a/src/test/test-condition.c
+++ b/src/test/test-condition.c
@@ -21,6 +21,7 @@
#include "errno-util.h"
#include "fileio.h"
#include "fs-util.h"
+#include "hostname-setup.h"
#include "hostname-util.h"
#include "id128-util.h"
#include "ima-util.h"
diff --git a/src/test/test-hostname-setup.c b/src/test/test-hostname-setup.c
index 2365a5edc11..da6e3796995 100644
--- a/src/test/test-hostname-setup.c
+++ b/src/test/test-hostname-setup.c
@@ -3,9 +3,12 @@
#include
#include "alloc-util.h"
+#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
#include "hostname-setup.h"
+#include "hostname-util.h"
+#include "id128-util.h"
#include "string-util.h"
#include "tests.h"
#include "tmpfile-util.h"
@@ -13,52 +16,95 @@
TEST(read_etc_hostname) {
_cleanup_(unlink_tempfilep) char path[] = "/tmp/hostname.XXXXXX";
char *hostname;
- int fd;
+ int r;
- fd = mkostemp_safe(path);
- assert_se(fd > 0);
- close(fd);
+ safe_close(ASSERT_FD(mkostemp_safe(path)));
/* simple hostname */
- assert_se(write_string_file(path, "foo", WRITE_STRING_FILE_CREATE) == 0);
- assert_se(read_etc_hostname(path, &hostname) == 0);
+ ASSERT_OK(write_string_file(path, "foo", WRITE_STRING_FILE_CREATE));
+ ASSERT_OK(read_etc_hostname(path, /* substitute_wildcards= */ false, &hostname));
ASSERT_STREQ(hostname, "foo");
hostname = mfree(hostname);
/* with comment */
- assert_se(write_string_file(path, "# comment\nfoo", WRITE_STRING_FILE_CREATE) == 0);
- assert_se(read_etc_hostname(path, &hostname) == 0);
- assert_se(hostname);
+ ASSERT_OK(write_string_file(path, "# comment\nfoo", WRITE_STRING_FILE_CREATE));
+ ASSERT_OK(read_etc_hostname(path, /* substitute_wildcards= */ false, &hostname));
+ ASSERT_NOT_NULL(hostname);
ASSERT_STREQ(hostname, "foo");
hostname = mfree(hostname);
/* with comment and extra whitespace */
- assert_se(write_string_file(path, "# comment\n\n foo ", WRITE_STRING_FILE_CREATE) == 0);
- assert_se(read_etc_hostname(path, &hostname) == 0);
- assert_se(hostname);
+ ASSERT_OK(write_string_file(path, "# comment\n\n foo ", WRITE_STRING_FILE_CREATE));
+ ASSERT_OK(read_etc_hostname(path, /* substitute_wildcards= */ false, &hostname));
+ ASSERT_NOT_NULL(hostname);
ASSERT_STREQ(hostname, "foo");
hostname = mfree(hostname);
/* cleans up name */
- assert_se(write_string_file(path, "!foo/bar.com", WRITE_STRING_FILE_CREATE) == 0);
- assert_se(read_etc_hostname(path, &hostname) == 0);
- assert_se(hostname);
+ ASSERT_OK(write_string_file(path, "!foo/bar.com", WRITE_STRING_FILE_CREATE));
+ ASSERT_OK(read_etc_hostname(path, /* substitute_wildcards= */ false, &hostname));
+ ASSERT_NOT_NULL(hostname);
ASSERT_STREQ(hostname, "foobar.com");
hostname = mfree(hostname);
+ /* with wildcards */
+ ASSERT_OK(write_string_file(path, "foo????????x??????????u", WRITE_STRING_FILE_CREATE));
+ ASSERT_OK(read_etc_hostname(path, /* substitute_wildcards= */ false, &hostname));
+ ASSERT_NOT_NULL(hostname);
+ ASSERT_STREQ(hostname, "foo????????x??????????u");
+ hostname = mfree(hostname);
+ r = read_etc_hostname(path, /* substitute_wildcards= */ true, &hostname);
+ if (ERRNO_IS_NEG_MACHINE_ID_UNSET(r))
+ log_tests_skipped("skipping wildcard hostname tests, no machine ID defined");
+ else {
+ ASSERT_OK(r);
+ ASSERT_NOT_NULL(hostname);
+ ASSERT_NULL(strchr(hostname, '?'));
+ ASSERT_EQ(fnmatch("foo????????x??????????u", hostname, /* flags= */ 0), 0);
+ ASSERT_TRUE(hostname_is_valid(hostname, /* flags= */ 0));
+ hostname = mfree(hostname);
+ }
+
/* no value set */
hostname = (char*) 0x1234;
- assert_se(write_string_file(path, "# nothing here\n", WRITE_STRING_FILE_CREATE) == 0);
- assert_se(read_etc_hostname(path, &hostname) == -ENOENT);
- assert_se(hostname == (char*) 0x1234); /* does not touch argument on error */
+ ASSERT_OK(write_string_file(path, "# nothing here\n", WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_TRUNCATE));
+ ASSERT_ERROR(read_etc_hostname(path, /* substitute_wildcards= */ false, &hostname), ENOENT);
+ assert(hostname == (char*) 0x1234); /* does not touch argument on error */
/* nonexisting file */
- assert_se(read_etc_hostname("/non/existing", &hostname) == -ENOENT);
- assert_se(hostname == (char*) 0x1234); /* does not touch argument on error */
+ ASSERT_ERROR(read_etc_hostname("/non/existing", /* substitute_wildcards= */ false, &hostname), ENOENT);
+ assert(hostname == (char*) 0x1234); /* does not touch argument on error */
}
TEST(hostname_setup) {
hostname_setup(false);
}
+TEST(hostname_malloc) {
+ _cleanup_free_ char *h = NULL, *l = NULL;
+
+ assert_se(h = gethostname_malloc());
+ log_info("hostname_malloc: \"%s\"", h);
+
+ assert_se(l = gethostname_short_malloc());
+ log_info("hostname_short_malloc: \"%s\"", l);
+}
+
+TEST(default_hostname) {
+ if (!hostname_is_valid(FALLBACK_HOSTNAME, 0)) {
+ log_error("Configured fallback hostname \"%s\" is not valid.", FALLBACK_HOSTNAME);
+ exit(EXIT_FAILURE);
+ }
+
+ _cleanup_free_ char *n = get_default_hostname();
+ ASSERT_NOT_NULL(n);
+ log_info("get_default_hostname: \"%s\"", n);
+ ASSERT_TRUE(hostname_is_valid(n, /* flags= */ 0));
+
+ _cleanup_free_ char *m = get_default_hostname_raw();
+ ASSERT_NOT_NULL(m);
+ log_info("get_default_hostname_raw: \"%s\"", m);
+ ASSERT_TRUE(hostname_is_valid(m, VALID_HOSTNAME_QUESTION_MARK));
+}
+
DEFINE_TEST_MAIN(LOG_DEBUG);
diff --git a/src/test/test-hostname-util.c b/src/test/test-hostname-util.c
index a7eccf8b35b..adfc6b2d6af 100644
--- a/src/test/test-hostname-util.c
+++ b/src/test/test-hostname-util.c
@@ -44,6 +44,9 @@ TEST(hostname_is_valid) {
assert_se(!hostname_is_valid("foo..bar", VALID_HOSTNAME_TRAILING_DOT));
assert_se(!hostname_is_valid("foo.bar..", VALID_HOSTNAME_TRAILING_DOT));
assert_se(!hostname_is_valid("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", VALID_HOSTNAME_TRAILING_DOT));
+
+ ASSERT_FALSE(hostname_is_valid("foo??bar", 0));
+ ASSERT_TRUE(hostname_is_valid("foo??bar", VALID_HOSTNAME_QUESTION_MARK));
}
TEST(hostname_cleanup) {
@@ -91,26 +94,4 @@ TEST(hostname_cleanup) {
ASSERT_STREQ(hostname_cleanup(s), "xxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
}
-TEST(hostname_malloc) {
- _cleanup_free_ char *h = NULL, *l = NULL;
-
- assert_se(h = gethostname_malloc());
- log_info("hostname_malloc: \"%s\"", h);
-
- assert_se(l = gethostname_short_malloc());
- log_info("hostname_short_malloc: \"%s\"", l);
-}
-
-TEST(default_hostname) {
- if (!hostname_is_valid(FALLBACK_HOSTNAME, 0)) {
- log_error("Configured fallback hostname \"%s\" is not valid.", FALLBACK_HOSTNAME);
- exit(EXIT_FAILURE);
- }
-
- _cleanup_free_ char *n = get_default_hostname();
- assert_se(n);
- log_info("get_default_hostname: \"%s\"", n);
- assert_se(hostname_is_valid(n, 0));
-}
-
DEFINE_TEST_MAIN(LOG_DEBUG);
diff --git a/src/test/test-load-fragment.c b/src/test/test-load-fragment.c
index 8883c4e4fc8..fdbfe1d1572 100644
--- a/src/test/test-load-fragment.c
+++ b/src/test/test-load-fragment.c
@@ -16,6 +16,7 @@
#include "format-util.h"
#include "fs-util.h"
#include "hashmap.h"
+#include "hostname-setup.h"
#include "hostname-util.h"
#include "install-printf.h"
#include "install.h"
diff --git a/src/test/test-nss-hosts.c b/src/test/test-nss-hosts.c
index 214fb217c85..08d198a02da 100644
--- a/src/test/test-nss-hosts.c
+++ b/src/test/test-nss-hosts.c
@@ -12,6 +12,7 @@
#include "format-ifname.h"
#include "hexdecoct.h"
#include "hostname-util.h"
+#include "hostname-setup.h"
#include "in-addr-util.h"
#include "local-addresses.h"
#include "log.h"
diff --git a/src/test/test-unit-name.c b/src/test/test-unit-name.c
index b4821047693..fcb61f2ca7f 100644
--- a/src/test/test-unit-name.c
+++ b/src/test/test-unit-name.c
@@ -9,6 +9,7 @@
#include "all-units.h"
#include "glob-util.h"
#include "format-util.h"
+#include "hostname-setup.h"
#include "hostname-util.h"
#include "macro.h"
#include "manager.h"
diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c
index d8b06b7ca6e..91259707aa4 100644
--- a/src/vmspawn/vmspawn.c
+++ b/src/vmspawn/vmspawn.c
@@ -38,6 +38,7 @@
#include "fs-util.h"
#include "gpt.h"
#include "hexdecoct.h"
+#include "hostname-setup.h"
#include "hostname-util.h"
#include "io-util.h"
#include "kernel-image.h"
diff --git a/test/units/TEST-71-HOSTNAME.sh b/test/units/TEST-71-HOSTNAME.sh
index 0813a07d46e..f844ccfcdb0 100755
--- a/test/units/TEST-71-HOSTNAME.sh
+++ b/test/units/TEST-71-HOSTNAME.sh
@@ -262,6 +262,22 @@ test_varlink() {
cmp "$A" "$B"
}
+test_wildcard() {
+ SAVED="$(cat /etc/hostname)"
+
+ P='foo-??-??.????bar'
+ hostnamectl set-hostname "$P"
+ H="$(hostname)"
+ # Validate that the hostname is not the literal pattern, but matches the pattern shell style
+ assert_neq "$H" "$P"
+ [[ "$P" == "$H" ]]
+ assert_eq "$(cat /etc/hostname)" "$P"
+
+ assert_in "Static hostname: foo-" "$(hostnamectl)"
+
+ hostnamectl set-hostname "$SAVED"
+}
+
run_testcases
touch /testok