]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/basic/hostname-util.c
Merge pull request #33079 from poettering/watchdog-no-disarm
[thirdparty/systemd.git] / src / basic / hostname-util.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
958b66ea 2
11c3a366
TA
3#include <errno.h>
4#include <limits.h>
5#include <stdio.h>
05c6f341 6#include <stdlib.h>
07630cea 7#include <sys/utsname.h>
11c3a366 8#include <unistd.h>
958b66ea 9
f35cb39e 10#include "alloc-util.h"
0da2bb74 11#include "env-file.h"
3ffd4af2 12#include "hostname-util.h"
05c6f341 13#include "os-util.h"
07630cea 14#include "string-util.h"
ddd6a22a 15#include "strv.h"
958b66ea 16
05c6f341 17char* get_default_hostname(void) {
e7637751
ZJS
18 int r;
19
05c6f341
ZJS
20 const char *e = secure_getenv("SYSTEMD_DEFAULT_HOSTNAME");
21 if (e) {
22 if (hostname_is_valid(e, 0))
23 return strdup(e);
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) {
32 if (hostname_is_valid(f, 0))
33 return TAKE_PTR(f);
34 log_debug("Invalid hostname in os-release, ignoring: %s", f);
35 }
36
05c6f341
ZJS
37 return strdup(FALLBACK_HOSTNAME);
38}
39
0995accd
YW
40int gethostname_full(GetHostnameFlags flags, char **ret) {
41 _cleanup_free_ char *buf = NULL, *fallback = NULL;
958b66ea 42 struct utsname u;
e97708fa 43 const char *s;
958b66ea 44
0995accd 45 assert(ret);
8e1ad1ea 46
958b66ea
LP
47 assert_se(uname(&u) >= 0);
48
e97708fa 49 s = u.nodename;
9383fa08 50 if (isempty(s) || streq(s, "(none)") ||
0995accd
YW
51 (!FLAGS_SET(flags, GET_HOSTNAME_ALLOW_LOCALHOST) && is_localhost(s)) ||
52 (FLAGS_SET(flags, GET_HOSTNAME_SHORT) && s[0] == '.')) {
53 if (!FLAGS_SET(flags, GET_HOSTNAME_FALLBACK_DEFAULT))
54 return -ENXIO;
55
56 s = fallback = get_default_hostname();
05c6f341 57 if (!s)
0995accd 58 return -ENOMEM;
05c6f341 59
0995accd
YW
60 if (FLAGS_SET(flags, GET_HOSTNAME_SHORT) && s[0] == '.')
61 return -ENXIO;
e97708fa
ZJS
62 }
63
0995accd 64 if (FLAGS_SET(flags, GET_HOSTNAME_SHORT))
e8bec624 65 buf = strdupcspn(s, ".");
0995accd
YW
66 else
67 buf = strdup(s);
68 if (!buf)
8e1ad1ea
LP
69 return -ENOMEM;
70
0995accd 71 *ret = TAKE_PTR(buf);
8e1ad1ea
LP
72 return 0;
73}
74
d65652f1 75bool valid_ldh_char(char c) {
9e815cf2
LP
76 /* "LDH" → "Letters, digits, hyphens", as per RFC 5890, Section 2.3.1 */
77
ff25d338
LP
78 return ascii_isalpha(c) ||
79 ascii_isdigit(c) ||
d65652f1 80 c == '-';
958b66ea
LP
81}
82
52ef5dd7 83bool hostname_is_valid(const char *s, ValidHostnameFlags flags) {
b59abc4d 84 unsigned n_dots = 0;
958b66ea 85 const char *p;
d65652f1 86 bool dot, hyphen;
958b66ea 87
52ef5dd7
LP
88 /* Check if s looks like a valid hostname or FQDN. This does not do full DNS validation, but only
89 * checks if the name is composed of allowed characters and the length is not above the maximum
90 * allowed by Linux (c.f. dns_name_is_valid()). A trailing dot is allowed if
91 * VALID_HOSTNAME_TRAILING_DOT flag is set and at least two components are present in the name. Note
92 * that due to the restricted charset and length this call is substantially more conservative than
93 * dns_name_is_valid(). Doesn't accept empty hostnames, hostnames with leading dots, and hostnames
94 * with multiple dots in a sequence. Doesn't allow hyphens at the beginning or end of label. */
95
958b66ea
LP
96 if (isempty(s))
97 return false;
98
52ef5dd7
LP
99 if (streq(s, ".host")) /* Used by the container logic to denote the "root container" */
100 return FLAGS_SET(flags, VALID_HOSTNAME_DOT_HOST);
958b66ea 101
d65652f1 102 for (p = s, dot = hyphen = true; *p; p++)
958b66ea 103 if (*p == '.') {
d65652f1 104 if (dot || hyphen)
958b66ea
LP
105 return false;
106
107 dot = true;
d65652f1 108 hyphen = false;
313cefa1 109 n_dots++;
d65652f1
ZJS
110
111 } else if (*p == '-') {
112 if (dot)
113 return false;
114
115 dot = false;
116 hyphen = true;
117
958b66ea 118 } else {
d65652f1 119 if (!valid_ldh_char(*p))
958b66ea
LP
120 return false;
121
122 dot = false;
d65652f1 123 hyphen = false;
958b66ea 124 }
958b66ea 125
52ef5dd7 126 if (dot && (n_dots < 2 || !FLAGS_SET(flags, VALID_HOSTNAME_TRAILING_DOT)))
958b66ea 127 return false;
d65652f1
ZJS
128 if (hyphen)
129 return false;
958b66ea 130
52ef5dd7
LP
131 if (p-s > HOST_NAME_MAX) /* Note that HOST_NAME_MAX is 64 on Linux, but DNS allows domain names up to
132 * 255 characters */
958b66ea
LP
133 return false;
134
135 return true;
136}
137
ae691c1d 138char* hostname_cleanup(char *s) {
958b66ea 139 char *p, *d;
d65652f1 140 bool dot, hyphen;
958b66ea
LP
141
142 assert(s);
143
5fe7a0a7 144 for (p = s, d = s, dot = hyphen = true; *p && d - s < HOST_NAME_MAX; p++)
958b66ea 145 if (*p == '.') {
d65652f1 146 if (dot || hyphen)
958b66ea
LP
147 continue;
148
149 *(d++) = '.';
150 dot = true;
d65652f1
ZJS
151 hyphen = false;
152
153 } else if (*p == '-') {
154 if (dot)
155 continue;
156
157 *(d++) = '-';
158 dot = false;
159 hyphen = true;
160
161 } else if (valid_ldh_char(*p)) {
ae691c1d 162 *(d++) = *p;
958b66ea 163 dot = false;
d65652f1 164 hyphen = false;
958b66ea 165 }
958b66ea 166
d65652f1
ZJS
167 if (d > s && IN_SET(d[-1], '-', '.'))
168 /* The dot can occur at most once, but we might have multiple
169 * hyphens, hence the loop */
170 d--;
171 *d = 0;
958b66ea 172
958b66ea
LP
173 return s;
174}
175
176bool is_localhost(const char *hostname) {
177 assert(hostname);
178
179 /* This tries to identify local host and domain names
63003524 180 * described in RFC6761 plus the redhatism of localdomain */
958b66ea 181
ddd6a22a
LP
182 return STRCASE_IN_SET(
183 hostname,
184 "localhost",
185 "localhost.",
186 "localhost.localdomain",
187 "localhost.localdomain.") ||
188 endswith_no_case(hostname, ".localhost") ||
189 endswith_no_case(hostname, ".localhost.") ||
190 endswith_no_case(hostname, ".localhost.localdomain") ||
191 endswith_no_case(hostname, ".localhost.localdomain.");
958b66ea 192}
0da2bb74
LP
193
194int get_pretty_hostname(char **ret) {
195 _cleanup_free_ char *n = NULL;
196 int r;
197
198 assert(ret);
199
200 r = parse_env_file(NULL, "/etc/machine-info", "PRETTY_HOSTNAME", &n);
201 if (r < 0)
202 return r;
203
204 if (isempty(n))
205 return -ENXIO;
206
207 *ret = TAKE_PTR(n);
208 return 0;
209}