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