]>
Commit | Line | Data |
---|---|---|
1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ | |
2 | ||
3 | #include <stdlib.h> | |
4 | ||
5 | #include "alloc-util.h" | |
6 | #include "env-file.h" | |
7 | #include "hostname-util.h" | |
8 | #include "log.h" | |
9 | #include "os-util.h" | |
10 | #include "string-util.h" | |
11 | #include "strv.h" | |
12 | #include "user-util.h" | |
13 | ||
14 | char* get_default_hostname_raw(void) { | |
15 | int r; | |
16 | ||
17 | /* Returns the default hostname, and leaves any ??? in place. */ | |
18 | ||
19 | const char *e = secure_getenv("SYSTEMD_DEFAULT_HOSTNAME"); | |
20 | if (e) { | |
21 | if (hostname_is_valid(e, VALID_HOSTNAME_QUESTION_MARK)) | |
22 | return strdup(e); | |
23 | ||
24 | log_debug("Invalid hostname in $SYSTEMD_DEFAULT_HOSTNAME, ignoring: %s", e); | |
25 | } | |
26 | ||
27 | _cleanup_free_ char *f = NULL; | |
28 | r = parse_os_release(NULL, "DEFAULT_HOSTNAME", &f); | |
29 | if (r < 0) | |
30 | log_debug_errno(r, "Failed to parse os-release, ignoring: %m"); | |
31 | else if (f) { | |
32 | if (hostname_is_valid(f, VALID_HOSTNAME_QUESTION_MARK)) | |
33 | return TAKE_PTR(f); | |
34 | ||
35 | log_debug("Invalid hostname in os-release, ignoring: %s", f); | |
36 | } | |
37 | ||
38 | return strdup(FALLBACK_HOSTNAME); | |
39 | } | |
40 | ||
41 | bool valid_ldh_char(char c) { | |
42 | /* "LDH" → "Letters, digits, hyphens", as per RFC 5890, Section 2.3.1 */ | |
43 | ||
44 | return ascii_isalpha(c) || | |
45 | ascii_isdigit(c) || | |
46 | c == '-'; | |
47 | } | |
48 | ||
49 | bool hostname_is_valid(const char *s, ValidHostnameFlags flags) { | |
50 | unsigned n_dots = 0; | |
51 | const char *p; | |
52 | bool dot, hyphen; | |
53 | ||
54 | /* Check if s looks like a valid hostname or FQDN. This does not do full DNS validation, but only | |
55 | * checks if the name is composed of allowed characters and the length is not above the maximum | |
56 | * allowed by Linux (c.f. dns_name_is_valid()). A trailing dot is allowed if | |
57 | * VALID_HOSTNAME_TRAILING_DOT flag is set and at least two components are present in the name. Note | |
58 | * that due to the restricted charset and length this call is substantially more conservative than | |
59 | * dns_name_is_valid(). Doesn't accept empty hostnames, hostnames with leading dots, and hostnames | |
60 | * with multiple dots in a sequence. Doesn't allow hyphens at the beginning or end of label. */ | |
61 | ||
62 | if (isempty(s)) | |
63 | return false; | |
64 | ||
65 | if (streq(s, ".host")) /* Used by the container logic to denote the "root container" */ | |
66 | return FLAGS_SET(flags, VALID_HOSTNAME_DOT_HOST); | |
67 | ||
68 | for (p = s, dot = hyphen = true; *p; p++) | |
69 | if (*p == '.') { | |
70 | if (dot || hyphen) | |
71 | return false; | |
72 | ||
73 | dot = true; | |
74 | hyphen = false; | |
75 | n_dots++; | |
76 | ||
77 | } else if (*p == '-') { | |
78 | if (dot) | |
79 | return false; | |
80 | ||
81 | dot = false; | |
82 | hyphen = true; | |
83 | ||
84 | } else { | |
85 | if (!valid_ldh_char(*p) && (*p != '?' || !FLAGS_SET(flags, VALID_HOSTNAME_QUESTION_MARK))) | |
86 | return false; | |
87 | ||
88 | dot = false; | |
89 | hyphen = false; | |
90 | } | |
91 | ||
92 | if (dot && (n_dots < 2 || !FLAGS_SET(flags, VALID_HOSTNAME_TRAILING_DOT))) | |
93 | return false; | |
94 | if (hyphen) | |
95 | return false; | |
96 | ||
97 | if (p-s > HOST_NAME_MAX) /* Note that HOST_NAME_MAX is 64 on Linux, but DNS allows domain names up to | |
98 | * 255 characters */ | |
99 | return false; | |
100 | ||
101 | return true; | |
102 | } | |
103 | ||
104 | char* hostname_cleanup(char *s) { | |
105 | char *p, *d; | |
106 | bool dot, hyphen; | |
107 | ||
108 | assert(s); | |
109 | ||
110 | for (p = s, d = s, dot = hyphen = true; *p && d - s < HOST_NAME_MAX; p++) | |
111 | if (*p == '.') { | |
112 | if (dot || hyphen) | |
113 | continue; | |
114 | ||
115 | *(d++) = '.'; | |
116 | dot = true; | |
117 | hyphen = false; | |
118 | ||
119 | } else if (*p == '-') { | |
120 | if (dot) | |
121 | continue; | |
122 | ||
123 | *(d++) = '-'; | |
124 | dot = false; | |
125 | hyphen = true; | |
126 | ||
127 | } else if (valid_ldh_char(*p) || *p == '?') { | |
128 | *(d++) = *p; | |
129 | dot = false; | |
130 | hyphen = false; | |
131 | } | |
132 | ||
133 | if (d > s && IN_SET(d[-1], '-', '.')) | |
134 | /* The dot can occur at most once, but we might have multiple | |
135 | * hyphens, hence the loop */ | |
136 | d--; | |
137 | *d = 0; | |
138 | ||
139 | return s; | |
140 | } | |
141 | ||
142 | bool is_localhost(const char *hostname) { | |
143 | assert(hostname); | |
144 | ||
145 | /* This tries to identify local host and domain names | |
146 | * described in RFC6761 plus the redhatism of localdomain */ | |
147 | ||
148 | return STRCASE_IN_SET( | |
149 | hostname, | |
150 | "localhost", | |
151 | "localhost.", | |
152 | "localhost.localdomain", | |
153 | "localhost.localdomain.") || | |
154 | endswith_no_case(hostname, ".localhost") || | |
155 | endswith_no_case(hostname, ".localhost.") || | |
156 | endswith_no_case(hostname, ".localhost.localdomain") || | |
157 | endswith_no_case(hostname, ".localhost.localdomain."); | |
158 | } | |
159 | ||
160 | const char* etc_hostname(void) { | |
161 | static const char *cached = NULL; | |
162 | ||
163 | if (!cached) | |
164 | cached = secure_getenv("SYSTEMD_ETC_HOSTNAME") ?: "/etc/hostname"; | |
165 | ||
166 | return cached; | |
167 | } | |
168 | ||
169 | const char* etc_machine_info(void) { | |
170 | static const char *cached = NULL; | |
171 | ||
172 | if (!cached) | |
173 | cached = secure_getenv("SYSTEMD_ETC_MACHINE_INFO") ?: "/etc/machine-info"; | |
174 | ||
175 | return cached; | |
176 | } | |
177 | ||
178 | int get_pretty_hostname(char **ret) { | |
179 | _cleanup_free_ char *n = NULL; | |
180 | int r; | |
181 | ||
182 | assert(ret); | |
183 | ||
184 | r = parse_env_file(NULL, etc_machine_info(), "PRETTY_HOSTNAME", &n); | |
185 | if (r < 0) | |
186 | return r; | |
187 | ||
188 | if (isempty(n)) | |
189 | return -ENXIO; | |
190 | ||
191 | *ret = TAKE_PTR(n); | |
192 | return 0; | |
193 | } | |
194 | ||
195 | int split_user_at_host(const char *s, char **ret_user, char **ret_host) { | |
196 | _cleanup_free_ char *u = NULL, *h = NULL; | |
197 | ||
198 | /* Splits a user@host expression (one of those we accept on --machine= and similar). Returns NULL in | |
199 | * each of the two return parameters if that part was left empty. */ | |
200 | ||
201 | assert(s); | |
202 | ||
203 | const char *rhs = strchr(s, '@'); | |
204 | if (rhs) { | |
205 | if (ret_user && rhs > s) { | |
206 | u = strndup(s, rhs - s); | |
207 | if (!u) | |
208 | return -ENOMEM; | |
209 | } | |
210 | ||
211 | if (ret_host && rhs[1] != 0) { | |
212 | h = strdup(rhs + 1); | |
213 | if (!h) | |
214 | return -ENOMEM; | |
215 | } | |
216 | ||
217 | } else { | |
218 | if (isempty(s)) | |
219 | return -EINVAL; | |
220 | ||
221 | if (ret_host) { | |
222 | h = strdup(s); | |
223 | if (!h) | |
224 | return -ENOMEM; | |
225 | } | |
226 | } | |
227 | ||
228 | if (ret_user) | |
229 | *ret_user = TAKE_PTR(u); | |
230 | if (ret_host) | |
231 | *ret_host = TAKE_PTR(h); | |
232 | ||
233 | return !!rhs; /* return > 0 if '@' was specified, 0 otherwise */ | |
234 | } | |
235 | ||
236 | int machine_spec_valid(const char *s) { | |
237 | _cleanup_free_ char *u = NULL, *h = NULL; | |
238 | int r; | |
239 | ||
240 | assert(s); | |
241 | ||
242 | r = split_user_at_host(s, &u, &h); | |
243 | if (r == -EINVAL) | |
244 | return false; | |
245 | if (r < 0) | |
246 | return r; | |
247 | ||
248 | if (u && !valid_user_group_name(u, VALID_USER_RELAX | VALID_USER_ALLOW_NUMERIC)) | |
249 | return false; | |
250 | ||
251 | if (h && !hostname_is_valid(h, VALID_HOSTNAME_DOT_HOST)) | |
252 | return false; | |
253 | ||
254 | return true; | |
255 | } |