1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
5 #include "alloc-util.h"
7 #include "hostname-util.h"
10 #include "string-util.h"
12 #include "user-util.h"
14 char* get_default_hostname_raw(void) {
17 /* Returns the default hostname, and leaves any ??? in place. */
19 const char *e
= secure_getenv("SYSTEMD_DEFAULT_HOSTNAME");
21 if (hostname_is_valid(e
, VALID_HOSTNAME_QUESTION_MARK
|VALID_HOSTNAME_WORD_TOKEN
))
24 log_debug("Invalid hostname in $SYSTEMD_DEFAULT_HOSTNAME, ignoring: %s", e
);
27 _cleanup_free_
char *f
= NULL
;
28 r
= parse_os_release(NULL
, "DEFAULT_HOSTNAME", &f
);
30 log_debug_errno(r
, "Failed to parse os-release, ignoring: %m");
32 if (hostname_is_valid(f
, VALID_HOSTNAME_QUESTION_MARK
|VALID_HOSTNAME_WORD_TOKEN
))
35 log_debug("Invalid hostname in os-release, ignoring: %s", f
);
38 return strdup(FALLBACK_HOSTNAME
);
41 bool valid_ldh_char(char c
) {
42 /* "LDH" → "Letters, digits, hyphens", as per RFC 5890, Section 2.3.1 */
44 return ascii_isalpha(c
) ||
49 bool hostname_is_valid(const char *s
, ValidHostnameFlags flags
) {
54 /* Check if s looks like a valid hostname or FQDN. This does not do full DNS validation, but only
55 * checks if the name is composed of allowed characters and the length is not above the maximum
56 * allowed by Linux (c.f. dns_name_is_valid()). A trailing dot is allowed if
57 * VALID_HOSTNAME_TRAILING_DOT flag is set and at least two components are present in the name. Note
58 * that due to the restricted charset and length this call is substantially more conservative than
59 * dns_name_is_valid(). Doesn't accept empty hostnames, hostnames with leading dots, and hostnames
60 * with multiple dots in a sequence. Doesn't allow hyphens at the beginning or end of label. */
65 if (streq(s
, ".host")) /* Used by the container logic to denote the "root container" */
66 return FLAGS_SET(flags
, VALID_HOSTNAME_DOT_HOST
);
68 for (p
= s
, dot
= hyphen
= true; *p
; p
++)
77 } else if (*p
== '-') {
85 if (!valid_ldh_char(*p
) &&
86 (*p
!= '?' || !FLAGS_SET(flags
, VALID_HOSTNAME_QUESTION_MARK
)) &&
87 (*p
!= '$' || !FLAGS_SET(flags
, VALID_HOSTNAME_WORD_TOKEN
)))
94 if (dot
&& (n_dots
< 2 || !FLAGS_SET(flags
, VALID_HOSTNAME_TRAILING_DOT
)))
99 /* Note that host name max is 64 on Linux, but DNS allows domain names up to 255 characters. */
100 if (p
- s
> (ssize_t
) LINUX_HOST_NAME_MAX
)
106 char* hostname_cleanup(char *s
) {
112 for (p
= s
, d
= s
, dot
= hyphen
= true; *p
&& d
- s
< (ssize_t
) LINUX_HOST_NAME_MAX
; p
++)
121 } else if (*p
== '-') {
129 } else if (valid_ldh_char(*p
) || IN_SET(*p
, '?', '$')) {
135 if (d
> s
&& IN_SET(d
[-1], '-', '.'))
136 /* The dot can occur at most once, but we might have multiple
137 * hyphens, hence the loop */
144 bool is_localhost(const char *hostname
) {
147 /* This tries to identify local host and domain names
148 * described in RFC6761 plus the redhatism of localdomain */
150 return STRCASE_IN_SET(
154 "localhost.localdomain",
155 "localhost.localdomain.") ||
156 endswith_no_case(hostname
, ".localhost") ||
157 endswith_no_case(hostname
, ".localhost.") ||
158 endswith_no_case(hostname
, ".localhost.localdomain") ||
159 endswith_no_case(hostname
, ".localhost.localdomain.");
162 const char* etc_hostname(void) {
163 static const char *cached
= NULL
;
166 cached
= secure_getenv("SYSTEMD_ETC_HOSTNAME") ?: "/etc/hostname";
171 const char* etc_machine_info(void) {
172 static const char *cached
= NULL
;
175 cached
= secure_getenv("SYSTEMD_ETC_MACHINE_INFO") ?: "/etc/machine-info";
180 int get_pretty_hostname(char **ret
) {
181 _cleanup_free_
char *n
= NULL
;
186 r
= parse_env_file(NULL
, etc_machine_info(), "PRETTY_HOSTNAME", &n
);
197 int split_user_at_host(const char *s
, char **ret_user
, char **ret_host
) {
198 _cleanup_free_
char *u
= NULL
, *h
= NULL
;
200 /* Splits a user@host expression (one of those we accept on --machine= and similar). Returns NULL in
201 * each of the two return parameters if that part was left empty. */
205 const char *rhs
= strchr(s
, '@');
207 if (ret_user
&& rhs
> s
) {
208 u
= strndup(s
, rhs
- s
);
213 if (ret_host
&& rhs
[1] != 0) {
231 *ret_user
= TAKE_PTR(u
);
233 *ret_host
= TAKE_PTR(h
);
235 return !!rhs
; /* return > 0 if '@' was specified, 0 otherwise */
238 int machine_spec_valid(const char *s
) {
239 _cleanup_free_
char *u
= NULL
, *h
= NULL
;
244 r
= split_user_at_host(s
, &u
, &h
);
250 if (u
&& !valid_user_group_name(u
, VALID_USER_RELAX
| VALID_USER_ALLOW_NUMERIC
))
253 if (h
&& !hostname_is_valid(h
, VALID_HOSTNAME_DOT_HOST
))
259 bool machine_tag_is_valid(const char *s
) {
260 size_t n
= strlen_ptr(s
);
261 if (n
<= 0 || n
>= 256)
264 /* Don't allow "-" and "." as first char. (This is load-bearing, we want that "+"/"-" can be used as
265 * prefix for adding/removing tags from the list). */
266 if (strchr("-.=", s
[0]))
269 /* We allow parameterization of tags, with a "=" as separator */
270 const char *eq
= strchr(s
, '=');
274 /* If there is an '=', then make the same restrictions as for the first char on the last char before it */
275 if (strchr("-.", eq
[-1]))
278 /* If there's no '=', then make the restriction on the very last character */
279 if (strchr("-.", s
[n
-1]))
283 return in_charset(s
, ALPHANUMERICAL
"-.=");
286 bool machine_tag_list_is_valid(char **l
) {
290 if (n
> MACHINE_TAGS_MAX
)
293 if (!machine_tag_is_valid(*i
))
296 const char *eq
= strchr(*i
, '=');
300 /* Refuse tags with a common part before the '=', that do no also carry the same value. */
301 size_t np
= eq
- *i
+ 1;
306 if (streq(*i
, *j
)) /* Fully identical is OK */
309 if (strneq(*i
, *j
, np
)) /* Not identical, but same key: refuse */
317 int machine_tags_from_string(const char *s
, bool graceful
, char ***ret
) {
322 /* Parses the colon-separated TAGS= machine-info field into a sorted, deduplicated strv. Each tag is
323 * validated: if 'graceful' is true invalid tags are silently dropped, otherwise an invalid tag makes
324 * us fail with -EINVAL. The result is NULL if no (valid) tags remain. */
331 _cleanup_strv_free_
char **l
= strv_split(s
, ":");
338 if (!machine_tag_list_is_valid(l
))
341 *ret
= strv_isempty(l
) ? NULL
: TAKE_PTR(l
);
346 _cleanup_strv_free_
char **cleaned
= NULL
;
348 if (!machine_tag_is_valid(*i
))
352 if (n
> MACHINE_TAGS_MAX
)
355 const char *eq
= strchr(*i
, '=');
357 /* Suppress duplicate assignments */
359 size_t np
= eq
- *i
+ 1;
360 STRV_FOREACH(j
, cleaned
)
361 if (strneq(*i
, *j
, np
)) {
370 r
= strv_extend(&cleaned
, *i
);
375 *ret
= TAKE_PTR(cleaned
);