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