1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <errno.h>
4 #include <limits.h>
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <sys/utsname.h>
8 #include <unistd.h>
9
10 #include "alloc-util.h"
11 #include "env-file.h"
12 #include "hostname-util.h"
13 #include "os-util.h"
14 #include "string-util.h"
15 #include "strv.h"
16
get_default_hostname(void)17 char* get_default_hostname(void) {
18 int r;
19
20 const char *e = secure_getenv("SYSTEMD_DEFAULT_HOSTNAME");
21 if (e) {
22 if (hostname_is_valid(e, 0))
23 return strdup(e);
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, 0))
33 return TAKE_PTR(f);
34 log_debug("Invalid hostname in os-release, ignoring: %s", f);
35 }
36
37 return strdup(FALLBACK_HOSTNAME);
38 }
39
gethostname_full(GetHostnameFlags flags,char ** ret)40 int gethostname_full(GetHostnameFlags flags, char **ret) {
41 _cleanup_free_ char *buf = NULL, *fallback = NULL;
42 struct utsname u;
43 const char *s;
44
45 assert(ret);
46
47 assert_se(uname(&u) >= 0);
48
49 s = u.nodename;
50 if (isempty(s) || streq(s, "(none)") ||
51 (!FLAGS_SET(flags, GET_HOSTNAME_ALLOW_LOCALHOST) && is_localhost(s)) ||
52 (FLAGS_SET(flags, GET_HOSTNAME_SHORT) && s[0] == '.')) {
53 if (!FLAGS_SET(flags, GET_HOSTNAME_FALLBACK_DEFAULT))
54 return -ENXIO;
55
56 s = fallback = get_default_hostname();
57 if (!s)
58 return -ENOMEM;
59
60 if (FLAGS_SET(flags, GET_HOSTNAME_SHORT) && s[0] == '.')
61 return -ENXIO;
62 }
63
64 if (FLAGS_SET(flags, GET_HOSTNAME_SHORT))
65 buf = strndup(s, strcspn(s, "."));
66 else
67 buf = strdup(s);
68 if (!buf)
69 return -ENOMEM;
70
71 *ret = TAKE_PTR(buf);
72 return 0;
73 }
74
valid_ldh_char(char c)75 bool valid_ldh_char(char c) {
76 /* "LDH" → "Letters, digits, hyphens", as per RFC 5890, Section 2.3.1 */
77
78 return
79 (c >= 'a' && c <= 'z') ||
80 (c >= 'A' && c <= 'Z') ||
81 (c >= '0' && c <= '9') ||
82 c == '-';
83 }
84
hostname_is_valid(const char * s,ValidHostnameFlags flags)85 bool hostname_is_valid(const char *s, ValidHostnameFlags flags) {
86 unsigned n_dots = 0;
87 const char *p;
88 bool dot, hyphen;
89
90 /* Check if s looks like a valid hostname or FQDN. This does not do full DNS validation, but only
91 * checks if the name is composed of allowed characters and the length is not above the maximum
92 * allowed by Linux (c.f. dns_name_is_valid()). A trailing dot is allowed if
93 * VALID_HOSTNAME_TRAILING_DOT flag is set and at least two components are present in the name. Note
94 * that due to the restricted charset and length this call is substantially more conservative than
95 * dns_name_is_valid(). Doesn't accept empty hostnames, hostnames with leading dots, and hostnames
96 * with multiple dots in a sequence. Doesn't allow hyphens at the beginning or end of label. */
97
98 if (isempty(s))
99 return false;
100
101 if (streq(s, ".host")) /* Used by the container logic to denote the "root container" */
102 return FLAGS_SET(flags, VALID_HOSTNAME_DOT_HOST);
103
104 for (p = s, dot = hyphen = true; *p; p++)
105 if (*p == '.') {
106 if (dot || hyphen)
107 return false;
108
109 dot = true;
110 hyphen = false;
111 n_dots++;
112
113 } else if (*p == '-') {
114 if (dot)
115 return false;
116
117 dot = false;
118 hyphen = true;
119
120 } else {
121 if (!valid_ldh_char(*p))
122 return false;
123
124 dot = false;
125 hyphen = false;
126 }
127
128 if (dot && (n_dots < 2 || !FLAGS_SET(flags, VALID_HOSTNAME_TRAILING_DOT)))
129 return false;
130 if (hyphen)
131 return false;
132
133 if (p-s > HOST_NAME_MAX) /* Note that HOST_NAME_MAX is 64 on Linux, but DNS allows domain names up to
134 * 255 characters */
135 return false;
136
137 return true;
138 }
139
hostname_cleanup(char * s)140 char* hostname_cleanup(char *s) {
141 char *p, *d;
142 bool dot, hyphen;
143
144 assert(s);
145
146 for (p = s, d = s, dot = hyphen = true; *p && d - s < HOST_NAME_MAX; p++)
147 if (*p == '.') {
148 if (dot || hyphen)
149 continue;
150
151 *(d++) = '.';
152 dot = true;
153 hyphen = false;
154
155 } else if (*p == '-') {
156 if (dot)
157 continue;
158
159 *(d++) = '-';
160 dot = false;
161 hyphen = true;
162
163 } else if (valid_ldh_char(*p)) {
164 *(d++) = *p;
165 dot = false;
166 hyphen = false;
167 }
168
169 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 d--;
173 *d = 0;
174
175 return s;
176 }
177
is_localhost(const char * hostname)178 bool is_localhost(const char *hostname) {
179 assert(hostname);
180
181 /* This tries to identify local host and domain names
182 * described in RFC6761 plus the redhatism of localdomain */
183
184 return STRCASE_IN_SET(
185 hostname,
186 "localhost",
187 "localhost.",
188 "localhost.localdomain",
189 "localhost.localdomain.") ||
190 endswith_no_case(hostname, ".localhost") ||
191 endswith_no_case(hostname, ".localhost.") ||
192 endswith_no_case(hostname, ".localhost.localdomain") ||
193 endswith_no_case(hostname, ".localhost.localdomain.");
194 }
195
get_pretty_hostname(char ** ret)196 int get_pretty_hostname(char **ret) {
197 _cleanup_free_ char *n = NULL;
198 int r;
199
200 assert(ret);
201
202 r = parse_env_file(NULL, "/etc/machine-info", "PRETTY_HOSTNAME", &n);
203 if (r < 0)
204 return r;
205
206 if (isempty(n))
207 return -ENXIO;
208
209 *ret = TAKE_PTR(n);
210 return 0;
211 }
212