]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
hostname: support that /etc/hostname contains ??? as wildcards to be replaced by...
authorLennart Poettering <lennart@poettering.net>
Thu, 6 Mar 2025 17:31:12 +0000 (18:31 +0100)
committerLennart Poettering <lennart@poettering.net>
Tue, 11 Mar 2025 17:01:42 +0000 (18:01 +0100)
16 files changed:
man/hostname.xml
man/hostnamectl.xml
man/os-release.xml
src/basic/hostname-util.c
src/basic/hostname-util.h
src/core/main.c
src/firstboot/firstboot.c
src/fuzz/fuzz-hostname-setup.c
src/hostname/hostnamectl.c
src/hostname/hostnamed.c
src/shared/discover-image.c
src/shared/dissect-image.c
src/shared/hostname-setup.c
src/shared/hostname-setup.h
src/test/test-hostname-setup.c
src/test/test-hostname-util.c

index 746de21cd12c063a95675cf965ec66d469f043f1..76acb8d7a5c130480b2403479f564746d9e822cb 100644 (file)
     attempt to make the name valid, but obviously it is recommended to use a valid name and not rely on this
     filtering.</para>
 
+    <para id="question-mark-hostname-pattern">If the question mark character <literal>?</literal> appears in
+    the hostname, it is automatically substituted by a hexadecimal character derived from the
+    <citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry> when
+    applied, securely and deterministically by cryptographic hashing. Example:
+    <literal>foobar-????-????</literal> will automatically expand to <literal>foobar-92a9-061c</literal> or
+    similar, depending on the local machine ID.</para>
+
     <para>You may use
     <citerefentry><refentrytitle>hostnamectl</refentrytitle><manvolnum>1</manvolnum></citerefentry> to change
     the value of this file during runtime from the command line. Use
index 70a6d295f6bcf0977eb4b523adaf3bed973662be..07cb0bc005aa31b0bb4dd2665346c747c349010c 100644 (file)
@@ -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).</para>
 
+        <xi:include href="hostname.xml" xpointer="question-mark-hostname-pattern"/>
+
         <xi:include href="version-info.xml" xpointer="v249"/></listitem>
       </varlistentry>
 
index 548eb47a4c0736f29609f569b50784de6b023fd2..54978edd43ce386fda28e58d3fa59c1e5141e884 100644 (file)
           that forms a valid DNS FQDN. The hostname must be at most 64 characters, which is a Linux
           limitation (DNS allows longer names).</para>
 
+          <xi:include href="hostname.xml" xpointer="question-mark-hostname-pattern"/>
+
           <para>See <citerefentry><refentrytitle>org.freedesktop.hostname1</refentrytitle><manvolnum>5</manvolnum></citerefentry>
           for a description of how
           <citerefentry><refentrytitle>systemd-hostnamed.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
index 165b1e2e16cd12ab1917d5cb008e6391c1aee7ba..7159473b582fa3400187bf3cb39e2e4bfaa5365e 100644 (file)
 #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,8 +32,9 @@ 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);
         }
 
@@ -81,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;
@@ -123,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;
index 4449c1eb3976a3aabce3ea11b9ae8295fce1c1b6..4c5abe760f01c765e4029370e5468818036e1b4d 100644 (file)
@@ -7,13 +7,14 @@
 #include "macro.h"
 #include "strv.h"
 
-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_;
index 0368ec891fcf60817ef103e8137b509fb6acc439..ee4b2d6bafb3eb9ec31cf7e98abd4d384b31a872 100644 (file)
@@ -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();
index 9ef8d89560e3db6f31dcfd695949951b57d2f28e..cd72f5439690e94411dccfe859ab447c3d33a934 100644 (file)
@@ -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);
 
index 4895631b67bcc268a3ef9fe3b1de146e44d53e6e..6ca0dc6fa535c8ff0a9680cf85c4a005fe06b8b9 100644 (file)
@@ -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;
 }
index bf03f177d37104874ed2f498de463dba73423c9a..144e3550a307f3a151fcbacacb7dcf94929d4430 100644 (file)
@@ -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 */
index 110f0a7400c8df08776e6b430705243303c08eb8..fb80e1280a6817fa622397e19bc73987b8866c96 100644 (file)
@@ -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, &current_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)),
index e99a0d6cae879535a77861750200bddad8d851a6..f9a1b2ffaa52477a55c5541f7557b754520091c7 100644 (file)
@@ -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);
                 }
index ef3a456ea7fa4e08cf0613793c0249ecf5ae4f8b..91f9d0c9ccfe2cf3a9b2e1140f78dcf781f7108e 100644 (file)
@@ -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");
 
index 1904885189aefbcfa67b596fa84611d84d3d98e6..af89f92bdc43b7c7bb182ffc97056c207db41950 100644 (file)
 #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)
@@ -238,6 +252,67 @@ 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;
index 3244c6c368dd780b2b8ba356aee8bec67ebc9b27..bc602acbfc477839c5c7426401e916b55c003ad1 100644 (file)
@@ -18,12 +18,16 @@ 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. */
index 67da2f03f11eccab29ce90b2d10a1cb173ee61d8..da6e379699566cd8882f288fb8508517d417ba90 100644 (file)
@@ -3,9 +3,12 @@
 #include <unistd.h>
 
 #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"
 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) {
@@ -71,4 +90,21 @@ TEST(hostname_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);
index 598ab96d91889962d14e65e79c0dc3544fd07f9a..adfc6b2d6af42d3571550052f46352d4fd9e8b01 100644 (file)
@@ -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,16 +94,4 @@ TEST(hostname_cleanup) {
         ASSERT_STREQ(hostname_cleanup(s), "xxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
 }
 
-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);