1
0
mirror of https://github.com/systemd/systemd.git synced 2025-03-22 06:50:18 +03:00

resolve question marks in /etc/hostname to characters hashed from machine ID (#36647)

So I have a bunch of particle os instances around, that I frequently
factory reset. and it's confusing, since they all have the same name.
Let's do something about this, and extend the hostname setup logic a bit
to deal better with "cattle" rather than "pet" deployments.
Specifically: if a hostname in /etc/hostname contains a bunch of
question marks we'll replace it with hex chars hashed from the machine
id.

This allows us to do something like this:

hostnamectl set-hostname --static 'funky-????-????-???'

and we'll end up with a hostname like `funky-baf4-b653-e230`
This commit is contained in:
Yu Watanabe 2025-03-12 04:50:33 +09:00 committed by GitHub
commit ced634a62d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 357 additions and 161 deletions

2
TODO
View File

@ -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

View File

@ -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.</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

View 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>

View File

@ -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).</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>

View File

@ -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;

View File

@ -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_;

View 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();

View 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);

View 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;
}

View 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 */

View 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)),

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View 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);
}

View 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");

View File

@ -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;
}

View File

@ -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;
}

View File

@ -3,12 +3,13 @@
#include <sys/auxv.h>
#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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View 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"
@ -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);

View 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,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);

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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