]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/basic/hostname-util.c
man/systemd-sysext: list ephemeral/ephemeral-import in the list of options
[thirdparty/systemd.git] / src / basic / hostname-util.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
958b66ea 2
05c6f341 3#include <stdlib.h>
958b66ea 4
f35cb39e 5#include "alloc-util.h"
0da2bb74 6#include "env-file.h"
3ffd4af2 7#include "hostname-util.h"
93a1f792 8#include "log.h"
05c6f341 9#include "os-util.h"
07630cea 10#include "string-util.h"
ddd6a22a 11#include "strv.h"
4c6fbe73 12#include "user-util.h"
958b66ea 13
af9c45d5 14char* get_default_hostname_raw(void) {
e7637751
ZJS
15 int r;
16
af9c45d5
LP
17 /* Returns the default hostname, and leaves any ??? in place. */
18
05c6f341
ZJS
19 const char *e = secure_getenv("SYSTEMD_DEFAULT_HOSTNAME");
20 if (e) {
af9c45d5 21 if (hostname_is_valid(e, VALID_HOSTNAME_QUESTION_MARK))
05c6f341 22 return strdup(e);
af9c45d5 23
05c6f341
ZJS
24 log_debug("Invalid hostname in $SYSTEMD_DEFAULT_HOSTNAME, ignoring: %s", e);
25 }
26
e7637751
ZJS
27 _cleanup_free_ char *f = NULL;
28 r = parse_os_release(NULL, "DEFAULT_HOSTNAME", &f);
29 if (r < 0)
30 log_debug_errno(r, "Failed to parse os-release, ignoring: %m");
31 else if (f) {
af9c45d5 32 if (hostname_is_valid(f, VALID_HOSTNAME_QUESTION_MARK))
e7637751 33 return TAKE_PTR(f);
af9c45d5 34
e7637751
ZJS
35 log_debug("Invalid hostname in os-release, ignoring: %s", f);
36 }
37
05c6f341
ZJS
38 return strdup(FALLBACK_HOSTNAME);
39}
40
d65652f1 41bool valid_ldh_char(char c) {
9e815cf2
LP
42 /* "LDH" → "Letters, digits, hyphens", as per RFC 5890, Section 2.3.1 */
43
ff25d338
LP
44 return ascii_isalpha(c) ||
45 ascii_isdigit(c) ||
d65652f1 46 c == '-';
958b66ea
LP
47}
48
52ef5dd7 49bool hostname_is_valid(const char *s, ValidHostnameFlags flags) {
b59abc4d 50 unsigned n_dots = 0;
958b66ea 51 const char *p;
d65652f1 52 bool dot, hyphen;
958b66ea 53
52ef5dd7
LP
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. */
61
958b66ea
LP
62 if (isempty(s))
63 return false;
64
52ef5dd7
LP
65 if (streq(s, ".host")) /* Used by the container logic to denote the "root container" */
66 return FLAGS_SET(flags, VALID_HOSTNAME_DOT_HOST);
958b66ea 67
d65652f1 68 for (p = s, dot = hyphen = true; *p; p++)
958b66ea 69 if (*p == '.') {
d65652f1 70 if (dot || hyphen)
958b66ea
LP
71 return false;
72
73 dot = true;
d65652f1 74 hyphen = false;
313cefa1 75 n_dots++;
d65652f1
ZJS
76
77 } else if (*p == '-') {
78 if (dot)
79 return false;
80
81 dot = false;
82 hyphen = true;
83
958b66ea 84 } else {
af9c45d5 85 if (!valid_ldh_char(*p) && (*p != '?' || !FLAGS_SET(flags, VALID_HOSTNAME_QUESTION_MARK)))
958b66ea
LP
86 return false;
87
88 dot = false;
d65652f1 89 hyphen = false;
958b66ea 90 }
958b66ea 91
52ef5dd7 92 if (dot && (n_dots < 2 || !FLAGS_SET(flags, VALID_HOSTNAME_TRAILING_DOT)))
958b66ea 93 return false;
d65652f1
ZJS
94 if (hyphen)
95 return false;
958b66ea 96
52ef5dd7
LP
97 if (p-s > HOST_NAME_MAX) /* Note that HOST_NAME_MAX is 64 on Linux, but DNS allows domain names up to
98 * 255 characters */
958b66ea
LP
99 return false;
100
101 return true;
102}
103
ae691c1d 104char* hostname_cleanup(char *s) {
958b66ea 105 char *p, *d;
d65652f1 106 bool dot, hyphen;
958b66ea
LP
107
108 assert(s);
109
5fe7a0a7 110 for (p = s, d = s, dot = hyphen = true; *p && d - s < HOST_NAME_MAX; p++)
958b66ea 111 if (*p == '.') {
d65652f1 112 if (dot || hyphen)
958b66ea
LP
113 continue;
114
115 *(d++) = '.';
116 dot = true;
d65652f1
ZJS
117 hyphen = false;
118
119 } else if (*p == '-') {
120 if (dot)
121 continue;
122
123 *(d++) = '-';
124 dot = false;
125 hyphen = true;
126
af9c45d5 127 } else if (valid_ldh_char(*p) || *p == '?') {
ae691c1d 128 *(d++) = *p;
958b66ea 129 dot = false;
d65652f1 130 hyphen = false;
958b66ea 131 }
958b66ea 132
d65652f1
ZJS
133 if (d > s && IN_SET(d[-1], '-', '.'))
134 /* The dot can occur at most once, but we might have multiple
135 * hyphens, hence the loop */
136 d--;
137 *d = 0;
958b66ea 138
958b66ea
LP
139 return s;
140}
141
142bool is_localhost(const char *hostname) {
143 assert(hostname);
144
145 /* This tries to identify local host and domain names
63003524 146 * described in RFC6761 plus the redhatism of localdomain */
958b66ea 147
ddd6a22a
LP
148 return STRCASE_IN_SET(
149 hostname,
150 "localhost",
151 "localhost.",
152 "localhost.localdomain",
153 "localhost.localdomain.") ||
154 endswith_no_case(hostname, ".localhost") ||
155 endswith_no_case(hostname, ".localhost.") ||
156 endswith_no_case(hostname, ".localhost.localdomain") ||
157 endswith_no_case(hostname, ".localhost.localdomain.");
958b66ea 158}
0da2bb74 159
0dc39dff
VD
160const char* etc_hostname(void) {
161 static const char *cached = NULL;
162
163 if (!cached)
164 cached = secure_getenv("SYSTEMD_ETC_HOSTNAME") ?: "/etc/hostname";
165
166 return cached;
167}
168
169const char* etc_machine_info(void) {
170 static const char *cached = NULL;
171
172 if (!cached)
173 cached = secure_getenv("SYSTEMD_ETC_MACHINE_INFO") ?: "/etc/machine-info";
174
175 return cached;
176}
177
0da2bb74
LP
178int get_pretty_hostname(char **ret) {
179 _cleanup_free_ char *n = NULL;
180 int r;
181
182 assert(ret);
183
0dc39dff 184 r = parse_env_file(NULL, etc_machine_info(), "PRETTY_HOSTNAME", &n);
0da2bb74
LP
185 if (r < 0)
186 return r;
187
188 if (isempty(n))
189 return -ENXIO;
190
191 *ret = TAKE_PTR(n);
192 return 0;
193}
4295c0db
LP
194
195int split_user_at_host(const char *s, char **ret_user, char **ret_host) {
196 _cleanup_free_ char *u = NULL, *h = NULL;
197
198 /* Splits a user@host expression (one of those we accept on --machine= and similar). Returns NULL in
199 * each of the two return parameters if that part was left empty. */
200
d2e727bf
MY
201 assert(s);
202
4295c0db
LP
203 const char *rhs = strchr(s, '@');
204 if (rhs) {
205 if (ret_user && rhs > s) {
206 u = strndup(s, rhs - s);
207 if (!u)
208 return -ENOMEM;
209 }
210
211 if (ret_host && rhs[1] != 0) {
212 h = strdup(rhs + 1);
213 if (!h)
214 return -ENOMEM;
215 }
d2e727bf
MY
216
217 } else {
218 if (isempty(s))
219 return -EINVAL;
220
221 if (ret_host) {
222 h = strdup(s);
223 if (!h)
224 return -ENOMEM;
225 }
4295c0db
LP
226 }
227
228 if (ret_user)
229 *ret_user = TAKE_PTR(u);
230 if (ret_host)
231 *ret_host = TAKE_PTR(h);
232
233 return !!rhs; /* return > 0 if '@' was specified, 0 otherwise */
234}
4c6fbe73
MY
235
236int machine_spec_valid(const char *s) {
237 _cleanup_free_ char *u = NULL, *h = NULL;
238 int r;
239
240 assert(s);
241
242 r = split_user_at_host(s, &u, &h);
243 if (r == -EINVAL)
244 return false;
245 if (r < 0)
246 return r;
247
248 if (u && !valid_user_group_name(u, VALID_USER_RELAX | VALID_USER_ALLOW_NUMERIC))
249 return false;
250
251 if (h && !hostname_is_valid(h, VALID_HOSTNAME_DOT_HOST))
252 return false;
253
254 return true;
255}