]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/basic/hostname-util.c
Merge pull request #15651 from poettering/newlocale-check
[thirdparty/systemd.git] / src / basic / hostname-util.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
958b66ea 2
11c3a366
TA
3#include <errno.h>
4#include <limits.h>
5#include <stdio.h>
07630cea 6#include <sys/utsname.h>
11c3a366 7#include <unistd.h>
958b66ea 8
f35cb39e 9#include "alloc-util.h"
3ffd4af2 10#include "fd-util.h"
0d39fa9c 11#include "fileio.h"
3ffd4af2 12#include "hostname-util.h"
11c3a366 13#include "macro.h"
07630cea 14#include "string-util.h"
ddd6a22a 15#include "strv.h"
958b66ea
LP
16
17bool hostname_is_set(void) {
18 struct utsname u;
19
20 assert_se(uname(&u) >= 0);
21
22 if (isempty(u.nodename))
23 return false;
24
38b38500 25 /* This is the built-in kernel default hostname */
958b66ea
LP
26 if (streq(u.nodename, "(none)"))
27 return false;
28
29 return true;
30}
31
32char* gethostname_malloc(void) {
33 struct utsname u;
e97708fa 34 const char *s;
958b66ea 35
953d28cc
ZJS
36 /* This call tries to return something useful, either the actual hostname
37 * or it makes something up. The only reason it might fail is OOM.
38 * It might even return "localhost" if that's set. */
8e1ad1ea 39
958b66ea
LP
40 assert_se(uname(&u) >= 0);
41
e97708fa
ZJS
42 s = u.nodename;
43 if (isempty(s) || streq(s, "(none)"))
44 s = FALLBACK_HOSTNAME;
958b66ea 45
e97708fa
ZJS
46 return strdup(s);
47}
48
49char* gethostname_short_malloc(void) {
50 struct utsname u;
51 const char *s;
52
53 /* Like above, but kills the FQDN part if present. */
54
55 assert_se(uname(&u) >= 0);
56
57 s = u.nodename;
58 if (isempty(s) || streq(s, "(none)") || s[0] == '.') {
59 s = FALLBACK_HOSTNAME;
60 assert(s[0] != '.');
61 }
62
63 return strndup(s, strcspn(s, "."));
958b66ea
LP
64}
65
8e1ad1ea
LP
66int gethostname_strict(char **ret) {
67 struct utsname u;
68 char *k;
69
70 /* This call will rather fail than make up a name. It will not return "localhost" either. */
71
72 assert_se(uname(&u) >= 0);
73
74 if (isempty(u.nodename))
75 return -ENXIO;
76
77 if (streq(u.nodename, "(none)"))
78 return -ENXIO;
79
80 if (is_localhost(u.nodename))
81 return -ENXIO;
82
83 k = strdup(u.nodename);
84 if (!k)
85 return -ENOMEM;
86
87 *ret = k;
88 return 0;
89}
90
d65652f1 91bool valid_ldh_char(char c) {
958b66ea
LP
92 return
93 (c >= 'a' && c <= 'z') ||
94 (c >= 'A' && c <= 'Z') ||
95 (c >= '0' && c <= '9') ||
d65652f1 96 c == '-';
958b66ea
LP
97}
98
8fb49443 99/**
38b38500 100 * Check if s looks like a valid hostname or FQDN. This does not do
8fb49443
ZJS
101 * full DNS validation, but only checks if the name is composed of
102 * allowed characters and the length is not above the maximum allowed
103 * by Linux (c.f. dns_name_is_valid()). Trailing dot is allowed if
b59abc4d
LP
104 * allow_trailing_dot is true and at least two components are present
105 * in the name. Note that due to the restricted charset and length
106 * this call is substantially more conservative than
06d91ad7 107 * dns_name_is_valid().
8fb49443 108 */
b59abc4d
LP
109bool hostname_is_valid(const char *s, bool allow_trailing_dot) {
110 unsigned n_dots = 0;
958b66ea 111 const char *p;
d65652f1 112 bool dot, hyphen;
958b66ea
LP
113
114 if (isempty(s))
115 return false;
116
8fb49443 117 /* Doesn't accept empty hostnames, hostnames with
958b66ea
LP
118 * leading dots, and hostnames with multiple dots in a
119 * sequence. Also ensures that the length stays below
120 * HOST_NAME_MAX. */
121
d65652f1 122 for (p = s, dot = hyphen = true; *p; p++)
958b66ea 123 if (*p == '.') {
d65652f1 124 if (dot || hyphen)
958b66ea
LP
125 return false;
126
127 dot = true;
d65652f1 128 hyphen = false;
313cefa1 129 n_dots++;
d65652f1
ZJS
130
131 } else if (*p == '-') {
132 if (dot)
133 return false;
134
135 dot = false;
136 hyphen = true;
137
958b66ea 138 } else {
d65652f1 139 if (!valid_ldh_char(*p))
958b66ea
LP
140 return false;
141
142 dot = false;
d65652f1 143 hyphen = false;
958b66ea 144 }
958b66ea 145
b59abc4d 146 if (dot && (n_dots < 2 || !allow_trailing_dot))
958b66ea 147 return false;
d65652f1
ZJS
148 if (hyphen)
149 return false;
958b66ea 150
b59abc4d
LP
151 if (p-s > HOST_NAME_MAX) /* Note that HOST_NAME_MAX is 64 on
152 * Linux, but DNS allows domain names
153 * up to 255 characters */
958b66ea
LP
154 return false;
155
156 return true;
157}
158
ae691c1d 159char* hostname_cleanup(char *s) {
958b66ea 160 char *p, *d;
d65652f1 161 bool dot, hyphen;
958b66ea
LP
162
163 assert(s);
164
5fe7a0a7 165 for (p = s, d = s, dot = hyphen = true; *p && d - s < HOST_NAME_MAX; p++)
958b66ea 166 if (*p == '.') {
d65652f1 167 if (dot || hyphen)
958b66ea
LP
168 continue;
169
170 *(d++) = '.';
171 dot = true;
d65652f1
ZJS
172 hyphen = false;
173
174 } else if (*p == '-') {
175 if (dot)
176 continue;
177
178 *(d++) = '-';
179 dot = false;
180 hyphen = true;
181
182 } else if (valid_ldh_char(*p)) {
ae691c1d 183 *(d++) = *p;
958b66ea 184 dot = false;
d65652f1 185 hyphen = false;
958b66ea 186 }
958b66ea 187
d65652f1
ZJS
188 if (d > s && IN_SET(d[-1], '-', '.'))
189 /* The dot can occur at most once, but we might have multiple
190 * hyphens, hence the loop */
191 d--;
192 *d = 0;
958b66ea 193
958b66ea
LP
194 return s;
195}
196
197bool is_localhost(const char *hostname) {
198 assert(hostname);
199
200 /* This tries to identify local host and domain names
63003524 201 * described in RFC6761 plus the redhatism of localdomain */
958b66ea 202
ddd6a22a
LP
203 return STRCASE_IN_SET(
204 hostname,
205 "localhost",
206 "localhost.",
207 "localhost.localdomain",
208 "localhost.localdomain.") ||
209 endswith_no_case(hostname, ".localhost") ||
210 endswith_no_case(hostname, ".localhost.") ||
211 endswith_no_case(hostname, ".localhost.localdomain") ||
212 endswith_no_case(hostname, ".localhost.localdomain.");
958b66ea
LP
213}
214
46a5e0e7
LP
215bool is_gateway_hostname(const char *hostname) {
216 assert(hostname);
217
218 /* This tries to identify the valid syntaxes for the our
219 * synthetic "gateway" host. */
220
221 return
5248e7e1
ZJS
222 strcaseeq(hostname, "_gateway") || strcaseeq(hostname, "_gateway.")
223#if ENABLE_COMPAT_GATEWAY_HOSTNAME
224 || strcaseeq(hostname, "gateway") || strcaseeq(hostname, "gateway.")
225#endif
226 ;
46a5e0e7
LP
227}
228
958b66ea
LP
229int sethostname_idempotent(const char *s) {
230 char buf[HOST_NAME_MAX + 1] = {};
231
232 assert(s);
233
234 if (gethostname(buf, sizeof(buf)) < 0)
235 return -errno;
236
237 if (streq(buf, s))
238 return 0;
239
240 if (sethostname(s, strlen(s)) < 0)
241 return -errno;
242
243 return 1;
244}
139e5336 245
2de2abad
LB
246int shorten_overlong(const char *s, char **ret) {
247 char *h, *p;
248
249 /* Shorten an overlong name to HOST_NAME_MAX or to the first dot,
250 * whatever comes earlier. */
251
252 assert(s);
253
254 h = strdup(s);
255 if (!h)
256 return -ENOMEM;
257
258 if (hostname_is_valid(h, false)) {
259 *ret = h;
260 return 0;
261 }
262
263 p = strchr(h, '.');
264 if (p)
265 *p = 0;
266
267 strshorten(h, HOST_NAME_MAX);
268
269 if (!hostname_is_valid(h, false)) {
270 free(h);
271 return -EDOM;
272 }
273
274 *ret = h;
275 return 1;
276}
277
f35cb39e
LP
278int read_etc_hostname_stream(FILE *f, char **ret) {
279 int r;
139e5336 280
f35cb39e
LP
281 assert(f);
282 assert(ret);
139e5336 283
f35cb39e
LP
284 for (;;) {
285 _cleanup_free_ char *line = NULL;
286 char *p;
287
288 r = read_line(f, LONG_LINE_MAX, &line);
289 if (r < 0)
290 return r;
291 if (r == 0) /* EOF without any hostname? the file is empty, let's treat that exactly like no file at all: ENOENT */
292 return -ENOENT;
139e5336 293
f35cb39e
LP
294 p = strstrip(line);
295
296 /* File may have empty lines or comments, ignore them */
297 if (!IN_SET(*p, '\0', '#')) {
298 char *copy;
299
300 hostname_cleanup(p); /* normalize the hostname */
301
302 if (!hostname_is_valid(p, true)) /* check that the hostname we return is valid */
303 return -EBADMSG;
304
305 copy = strdup(p);
306 if (!copy)
139e5336 307 return -ENOMEM;
f35cb39e
LP
308
309 *ret = copy;
310 return 0;
139e5336
MP
311 }
312 }
f35cb39e 313}
139e5336 314
f35cb39e
LP
315int read_etc_hostname(const char *path, char **ret) {
316 _cleanup_fclose_ FILE *f = NULL;
317
318 assert(ret);
319
320 if (!path)
321 path = "/etc/hostname";
322
323 f = fopen(path, "re");
324 if (!f)
325 return -errno;
326
327 return read_etc_hostname_stream(f, ret);
139e5336 328
139e5336 329}