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