]>
Commit | Line | Data |
---|---|---|
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 | |
17 | bool 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 | ||
32 | char* 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 | ||
49 | char* 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 |
66 | int 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 | 91 | bool 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 |
109 | bool 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 | 159 | char* 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 | ||
197 | bool 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 |
215 | bool 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 |
229 | int 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 |
246 | int 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 |
278 | int 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 |
315 | int 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 | } |