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