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