Branch data 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 : 48 : 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 [ - + ]: 48 : assert_se(uname(&u) >= 0);
40 : :
41 [ + - - + ]: 48 : if (isempty(u.nodename) || streq(u.nodename, "(none)"))
42 : 0 : return strdup(FALLBACK_HOSTNAME);
43 : :
44 : 48 : 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 : 10732 : bool valid_ldh_char(char c) {
73 : : return
74 [ + + + + ]: 10732 : (c >= 'a' && c <= 'z') ||
75 [ + + + + ]: 4128 : (c >= 'A' && c <= 'Z') ||
76 [ + + + + : 21464 : (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 : 280 : bool hostname_is_valid(const char *s, bool allow_trailing_dot) {
91 : 280 : unsigned n_dots = 0;
92 : : const char *p;
93 : : bool dot, hyphen;
94 : :
95 [ + + ]: 280 : if (isempty(s))
96 : 20 : 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 [ + + ]: 3616 : for (p = s, dot = hyphen = true; *p; p++)
104 [ + + ]: 3428 : if (*p == '.') {
105 [ + + - + ]: 180 : if (dot || hyphen)
106 : 44 : return false;
107 : :
108 : 136 : dot = true;
109 : 136 : hyphen = false;
110 : 136 : n_dots++;
111 : :
112 [ + + ]: 3248 : } else if (*p == '-') {
113 [ - + ]: 156 : if (dot)
114 : 0 : return false;
115 : :
116 : 156 : dot = false;
117 : 156 : hyphen = true;
118 : :
119 : : } else {
120 [ + + ]: 3092 : if (!valid_ldh_char(*p))
121 : 28 : return false;
122 : :
123 : 3064 : dot = false;
124 : 3064 : hyphen = false;
125 : : }
126 : :
127 [ + + + + : 188 : if (dot && (n_dots < 2 || !allow_trailing_dot))
+ + ]
128 : 24 : return false;
129 [ - + ]: 164 : if (hyphen)
130 : 0 : return false;
131 : :
132 [ + + ]: 164 : 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 : 20 : return false;
136 : :
137 : 144 : return true;
138 : : }
139 : :
140 : 96 : char* hostname_cleanup(char *s) {
141 : : char *p, *d;
142 : : bool dot, hyphen;
143 : :
144 [ - + ]: 96 : assert(s);
145 : :
146 [ + + + + ]: 1304 : for (p = s, d = s, dot = hyphen = true; *p && d - s < HOST_NAME_MAX; p++)
147 [ + + ]: 1208 : if (*p == '.') {
148 [ + + + + ]: 124 : if (dot || hyphen)
149 : 64 : continue;
150 : :
151 : 60 : *(d++) = '.';
152 : 60 : dot = true;
153 : 60 : hyphen = false;
154 : :
155 [ + + ]: 1084 : } else if (*p == '-') {
156 [ + + ]: 44 : if (dot)
157 : 16 : continue;
158 : :
159 : 28 : *(d++) = '-';
160 : 28 : dot = false;
161 : 28 : hyphen = true;
162 : :
163 [ + + ]: 1040 : } else if (valid_ldh_char(*p)) {
164 : 1016 : *(d++) = *p;
165 : 1016 : dot = false;
166 : 1016 : hyphen = false;
167 : : }
168 : :
169 [ + + + + : 96 : 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 : 28 : d--;
173 : 96 : *d = 0;
174 : :
175 : 96 : return s;
176 : : }
177 : :
178 : 164 : bool is_localhost(const char *hostname) {
179 [ - + ]: 164 : assert(hostname);
180 : :
181 : : /* This tries to identify local host and domain names
182 : : * described in RFC6761 plus the redhatism of localdomain */
183 : :
184 : 324 : return strcaseeq(hostname, "localhost") ||
185 [ + - ]: 160 : strcaseeq(hostname, "localhost.") ||
186 [ + + ]: 160 : strcaseeq(hostname, "localhost.localdomain") ||
187 [ + - ]: 156 : strcaseeq(hostname, "localhost.localdomain.") ||
188 [ + - ]: 156 : endswith_no_case(hostname, ".localhost") ||
189 [ + - ]: 156 : endswith_no_case(hostname, ".localhost.") ||
190 [ + + + - ]: 480 : endswith_no_case(hostname, ".localhost.localdomain") ||
191 [ - + ]: 156 : 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 : 20 : 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 [ - + ]: 20 : assert(s);
232 : :
233 : 20 : h = strdup(s);
234 [ - + ]: 20 : if (!h)
235 : 0 : return -ENOMEM;
236 : :
237 [ + + ]: 20 : if (hostname_is_valid(h, false)) {
238 : 8 : *ret = h;
239 : 8 : return 0;
240 : : }
241 : :
242 : 12 : p = strchr(h, '.');
243 [ + + ]: 12 : if (p)
244 : 8 : *p = 0;
245 : :
246 : 12 : strshorten(h, HOST_NAME_MAX);
247 : :
248 [ + + ]: 12 : if (!hostname_is_valid(h, false)) {
249 : 4 : free(h);
250 : 4 : return -EDOM;
251 : : }
252 : :
253 : 8 : *ret = h;
254 : 8 : return 1;
255 : : }
256 : :
257 : 20 : int read_etc_hostname_stream(FILE *f, char **ret) {
258 : : int r;
259 : :
260 [ - + ]: 20 : assert(f);
261 [ - + ]: 20 : assert(ret);
262 : :
263 : 16 : for (;;) {
264 [ + + ]: 36 : _cleanup_free_ char *line = NULL;
265 : : char *p;
266 : :
267 : 36 : r = read_line(f, LONG_LINE_MAX, &line);
268 [ - + ]: 36 : if (r < 0)
269 : 0 : return r;
270 [ + + ]: 36 : if (r == 0) /* EOF without any hostname? the file is empty, let's treat that exactly like no file at all: ENOENT */
271 : 4 : return -ENOENT;
272 : :
273 : 32 : p = strstrip(line);
274 : :
275 : : /* File may have empty lines or comments, ignore them */
276 [ + + + + ]: 32 : if (!IN_SET(*p, '\0', '#')) {
277 : : char *copy;
278 : :
279 : 16 : hostname_cleanup(p); /* normalize the hostname */
280 : :
281 [ - + ]: 16 : if (!hostname_is_valid(p, true)) /* check that the hostname we return is valid */
282 : 0 : return -EBADMSG;
283 : :
284 : 16 : copy = strdup(p);
285 [ - + ]: 16 : if (!copy)
286 : 0 : return -ENOMEM;
287 : :
288 : 16 : *ret = copy;
289 : 16 : return 0;
290 : : }
291 : : }
292 : : }
293 : :
294 : 24 : int read_etc_hostname(const char *path, char **ret) {
295 : 24 : _cleanup_fclose_ FILE *f = NULL;
296 : :
297 [ - + ]: 24 : assert(ret);
298 : :
299 [ - + ]: 24 : if (!path)
300 : 0 : path = "/etc/hostname";
301 : :
302 : 24 : f = fopen(path, "re");
303 [ + + ]: 24 : if (!f)
304 : 4 : return -errno;
305 : :
306 : 20 : return read_etc_hostname_stream(f, ret);
307 : :
308 : : }
|