]> git.ipfire.org Git - thirdparty/systemd.git/blame_incremental - src/basic/hostname-util.c
hwdb: Make Amlogic burn mode work out-of-box
[thirdparty/systemd.git] / src / basic / hostname-util.c
... / ...
CommitLineData
1/* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3#include <stdlib.h>
4
5#include "alloc-util.h"
6#include "env-file.h"
7#include "hostname-util.h"
8#include "log.h"
9#include "os-util.h"
10#include "string-util.h"
11#include "strv.h"
12#include "user-util.h"
13
14char* get_default_hostname_raw(void) {
15 int r;
16
17 /* Returns the default hostname, and leaves any ??? in place. */
18
19 const char *e = secure_getenv("SYSTEMD_DEFAULT_HOSTNAME");
20 if (e) {
21 if (hostname_is_valid(e, VALID_HOSTNAME_QUESTION_MARK|VALID_HOSTNAME_WORD_TOKEN))
22 return strdup(e);
23
24 log_debug("Invalid hostname in $SYSTEMD_DEFAULT_HOSTNAME, ignoring: %s", e);
25 }
26
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) {
32 if (hostname_is_valid(f, VALID_HOSTNAME_QUESTION_MARK|VALID_HOSTNAME_WORD_TOKEN))
33 return TAKE_PTR(f);
34
35 log_debug("Invalid hostname in os-release, ignoring: %s", f);
36 }
37
38 return strdup(FALLBACK_HOSTNAME);
39}
40
41bool valid_ldh_char(char c) {
42 /* "LDH" → "Letters, digits, hyphens", as per RFC 5890, Section 2.3.1 */
43
44 return ascii_isalpha(c) ||
45 ascii_isdigit(c) ||
46 c == '-';
47}
48
49bool hostname_is_valid(const char *s, ValidHostnameFlags flags) {
50 unsigned n_dots = 0;
51 const char *p;
52 bool dot, hyphen;
53
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
62 if (isempty(s))
63 return false;
64
65 if (streq(s, ".host")) /* Used by the container logic to denote the "root container" */
66 return FLAGS_SET(flags, VALID_HOSTNAME_DOT_HOST);
67
68 for (p = s, dot = hyphen = true; *p; p++)
69 if (*p == '.') {
70 if (dot || hyphen)
71 return false;
72
73 dot = true;
74 hyphen = false;
75 n_dots++;
76
77 } else if (*p == '-') {
78 if (dot)
79 return false;
80
81 dot = false;
82 hyphen = true;
83
84 } else {
85 if (!valid_ldh_char(*p) &&
86 (*p != '?' || !FLAGS_SET(flags, VALID_HOSTNAME_QUESTION_MARK)) &&
87 (*p != '$' || !FLAGS_SET(flags, VALID_HOSTNAME_WORD_TOKEN)))
88 return false;
89
90 dot = false;
91 hyphen = false;
92 }
93
94 if (dot && (n_dots < 2 || !FLAGS_SET(flags, VALID_HOSTNAME_TRAILING_DOT)))
95 return false;
96 if (hyphen)
97 return false;
98
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)
101 return false;
102
103 return true;
104}
105
106char* hostname_cleanup(char *s) {
107 char *p, *d;
108 bool dot, hyphen;
109
110 assert(s);
111
112 for (p = s, d = s, dot = hyphen = true; *p && d - s < (ssize_t) LINUX_HOST_NAME_MAX; p++)
113 if (*p == '.') {
114 if (dot || hyphen)
115 continue;
116
117 *(d++) = '.';
118 dot = true;
119 hyphen = false;
120
121 } else if (*p == '-') {
122 if (dot)
123 continue;
124
125 *(d++) = '-';
126 dot = false;
127 hyphen = true;
128
129 } else if (valid_ldh_char(*p) || IN_SET(*p, '?', '$')) {
130 *(d++) = *p;
131 dot = false;
132 hyphen = false;
133 }
134
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 */
138 d--;
139 *d = 0;
140
141 return s;
142}
143
144bool is_localhost(const char *hostname) {
145 assert(hostname);
146
147 /* This tries to identify local host and domain names
148 * described in RFC6761 plus the redhatism of localdomain */
149
150 return STRCASE_IN_SET(
151 hostname,
152 "localhost",
153 "localhost.",
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.");
160}
161
162const char* etc_hostname(void) {
163 static const char *cached = NULL;
164
165 if (!cached)
166 cached = secure_getenv("SYSTEMD_ETC_HOSTNAME") ?: "/etc/hostname";
167
168 return cached;
169}
170
171const char* etc_machine_info(void) {
172 static const char *cached = NULL;
173
174 if (!cached)
175 cached = secure_getenv("SYSTEMD_ETC_MACHINE_INFO") ?: "/etc/machine-info";
176
177 return cached;
178}
179
180int get_pretty_hostname(char **ret) {
181 _cleanup_free_ char *n = NULL;
182 int r;
183
184 assert(ret);
185
186 r = parse_env_file(NULL, etc_machine_info(), "PRETTY_HOSTNAME", &n);
187 if (r < 0)
188 return r;
189
190 if (isempty(n))
191 return -ENXIO;
192
193 *ret = TAKE_PTR(n);
194 return 0;
195}
196
197int split_user_at_host(const char *s, char **ret_user, char **ret_host) {
198 _cleanup_free_ char *u = NULL, *h = NULL;
199
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. */
202
203 assert(s);
204
205 const char *rhs = strchr(s, '@');
206 if (rhs) {
207 if (ret_user && rhs > s) {
208 u = strndup(s, rhs - s);
209 if (!u)
210 return -ENOMEM;
211 }
212
213 if (ret_host && rhs[1] != 0) {
214 h = strdup(rhs + 1);
215 if (!h)
216 return -ENOMEM;
217 }
218
219 } else {
220 if (isempty(s))
221 return -EINVAL;
222
223 if (ret_host) {
224 h = strdup(s);
225 if (!h)
226 return -ENOMEM;
227 }
228 }
229
230 if (ret_user)
231 *ret_user = TAKE_PTR(u);
232 if (ret_host)
233 *ret_host = TAKE_PTR(h);
234
235 return !!rhs; /* return > 0 if '@' was specified, 0 otherwise */
236}
237
238int machine_spec_valid(const char *s) {
239 _cleanup_free_ char *u = NULL, *h = NULL;
240 int r;
241
242 assert(s);
243
244 r = split_user_at_host(s, &u, &h);
245 if (r == -EINVAL)
246 return false;
247 if (r < 0)
248 return r;
249
250 if (u && !valid_user_group_name(u, VALID_USER_RELAX | VALID_USER_ALLOW_NUMERIC))
251 return false;
252
253 if (h && !hostname_is_valid(h, VALID_HOSTNAME_DOT_HOST))
254 return false;
255
256 return true;
257}
258
259bool machine_tag_is_valid(const char *s) {
260 size_t n = strlen_ptr(s);
261 if (n <= 0 || n >= 256)
262 return false;
263
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]))
267 return false;
268
269 /* We allow parameterization of tags, with a "=" as separator */
270 const char *eq = strchr(s, '=');
271 if (eq) {
272 assert(eq > s);
273
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]))
276 return false;
277 } else {
278 /* If there's no '=', then make the restriction on the very last character */
279 if (strchr("-.", s[n-1]))
280 return false;
281 }
282
283 return in_charset(s, ALPHANUMERICAL "-.=");
284}
285
286bool machine_tag_list_is_valid(char **l) {
287 size_t n = 0;
288 STRV_FOREACH(i, l) {
289 n++;
290 if (n > MACHINE_TAGS_MAX)
291 return false;
292
293 if (!machine_tag_is_valid(*i))
294 return false;
295
296 const char *eq = strchr(*i, '=');
297 if (!eq)
298 continue;
299
300 /* Refuse tags with a common part before the '=', that do no also carry the same value. */
301 size_t np = eq - *i + 1;
302 STRV_FOREACH(j, l) {
303 if (j == i)
304 break;
305
306 if (streq(*i, *j)) /* Fully identical is OK */
307 continue;
308
309 if (strneq(*i, *j, np)) /* Not identical, but same key: refuse */
310 return false;
311 }
312 }
313
314 return true;
315}
316
317int machine_tags_from_string(const char *s, bool graceful, char ***ret) {
318 int r;
319
320 assert(ret);
321
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. */
325
326 if (isempty(s)) {
327 *ret = NULL;
328 return 0;
329 }
330
331 _cleanup_strv_free_ char **l = strv_split(s, ":");
332 if (!l)
333 return -ENOMEM;
334
335 strv_sort_uniq(l);
336
337 if (!graceful) {
338 if (!machine_tag_list_is_valid(l))
339 return -EINVAL;
340
341 *ret = strv_isempty(l) ? NULL : TAKE_PTR(l);
342 return 0;
343 }
344
345 size_t n = 0;
346 _cleanup_strv_free_ char **cleaned = NULL;
347 STRV_FOREACH(i, l) {
348 if (!machine_tag_is_valid(*i))
349 continue;
350
351 n++;
352 if (n > MACHINE_TAGS_MAX)
353 return -E2BIG;
354
355 const char *eq = strchr(*i, '=');
356 if (eq) {
357 /* Suppress duplicate assignments */
358 bool skip = false;
359 size_t np = eq - *i + 1;
360 STRV_FOREACH(j, cleaned)
361 if (strneq(*i, *j, np)) {
362 skip = true;
363 break;
364 }
365
366 if (skip)
367 continue;
368 }
369
370 r = strv_extend(&cleaned, *i);
371 if (r < 0)
372 return r;
373 }
374
375 *ret = TAKE_PTR(cleaned);
376 return 0;
377}