1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include <errno.h>
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <sys/utsname.h>
7 #include <unistd.h>
8 
9 #include "alloc-util.h"
10 #include "fd-util.h"
11 #include "fileio.h"
12 #include "fs-util.h"
13 #include "hostname-setup.h"
14 #include "hostname-util.h"
15 #include "log.h"
16 #include "macro.h"
17 #include "proc-cmdline.h"
18 #include "string-table.h"
19 #include "string-util.h"
20 #include "util.h"
21 
sethostname_idempotent_full(const char * s,bool really)22 static int sethostname_idempotent_full(const char *s, bool really) {
23         struct utsname u;
24 
25         assert(s);
26 
27         assert_se(uname(&u) >= 0);
28 
29         if (streq_ptr(s, u.nodename))
30                 return 0;
31 
32         if (really &&
33             sethostname(s, strlen(s)) < 0)
34                 return -errno;
35 
36         return 1;
37 }
38 
sethostname_idempotent(const char * s)39 int sethostname_idempotent(const char *s) {
40         return sethostname_idempotent_full(s, true);
41 }
42 
shorten_overlong(const char * s,char ** ret)43 int shorten_overlong(const char *s, char **ret) {
44         char *h, *p;
45 
46         /* Shorten an overlong name to HOST_NAME_MAX or to the first dot,
47          * whatever comes earlier. */
48 
49         assert(s);
50 
51         h = strdup(s);
52         if (!h)
53                 return -ENOMEM;
54 
55         if (hostname_is_valid(h, 0)) {
56                 *ret = h;
57                 return 0;
58         }
59 
60         p = strchr(h, '.');
61         if (p)
62                 *p = 0;
63 
64         strshorten(h, HOST_NAME_MAX);
65 
66         if (!hostname_is_valid(h, 0)) {
67                 free(h);
68                 return -EDOM;
69         }
70 
71         *ret = h;
72         return 1;
73 }
74 
read_etc_hostname_stream(FILE * f,char ** ret)75 int read_etc_hostname_stream(FILE *f, char **ret) {
76         int r;
77 
78         assert(f);
79         assert(ret);
80 
81         for (;;) {
82                 _cleanup_free_ char *line = NULL;
83                 char *p;
84 
85                 r = read_line(f, LONG_LINE_MAX, &line);
86                 if (r < 0)
87                         return r;
88                 if (r == 0) /* EOF without any hostname? the file is empty, let's treat that exactly like no file at all: ENOENT */
89                         return -ENOENT;
90 
91                 p = strstrip(line);
92 
93                 /* File may have empty lines or comments, ignore them */
94                 if (!IN_SET(*p, '\0', '#')) {
95                         char *copy;
96 
97                         hostname_cleanup(p); /* normalize the hostname */
98 
99                         if (!hostname_is_valid(p, VALID_HOSTNAME_TRAILING_DOT)) /* check that the hostname we return is valid */
100                                 return -EBADMSG;
101 
102                         copy = strdup(p);
103                         if (!copy)
104                                 return -ENOMEM;
105 
106                         *ret = copy;
107                         return 0;
108                 }
109         }
110 }
111 
read_etc_hostname(const char * path,char ** ret)112 int read_etc_hostname(const char *path, char **ret) {
113         _cleanup_fclose_ FILE *f = NULL;
114 
115         assert(ret);
116 
117         if (!path)
118                 path = "/etc/hostname";
119 
120         f = fopen(path, "re");
121         if (!f)
122                 return -errno;
123 
124         return read_etc_hostname_stream(f, ret);
125 }
126 
hostname_update_source_hint(const char * hostname,HostnameSource source)127 void hostname_update_source_hint(const char *hostname, HostnameSource source) {
128         int r;
129 
130         /* Why save the value and not just create a flag file? This way we will
131          * notice if somebody sets the hostname directly (not going through hostnamed).
132          */
133 
134         if (source == HOSTNAME_DEFAULT) {
135                 r = write_string_file("/run/systemd/default-hostname", hostname,
136                                       WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_ATOMIC);
137                 if (r < 0)
138                         log_warning_errno(r, "Failed to create \"/run/systemd/default-hostname\": %m");
139         } else
140                 unlink_or_warn("/run/systemd/default-hostname");
141 }
142 
hostname_setup(bool really)143 int hostname_setup(bool really) {
144         _cleanup_free_ char *b = NULL;
145         const char *hn = NULL;
146         HostnameSource source;
147         bool enoent = false;
148         int r;
149 
150         r = proc_cmdline_get_key("systemd.hostname", 0, &b);
151         if (r < 0)
152                 log_warning_errno(r, "Failed to retrieve system hostname from kernel command line, ignoring: %m");
153         else if (r > 0) {
154                 if (hostname_is_valid(b, true)) {
155                         hn = b;
156                         source = HOSTNAME_TRANSIENT;
157                 } else  {
158                         log_warning("Hostname specified on kernel command line is invalid, ignoring: %s", b);
159                         b = mfree(b);
160                 }
161         }
162 
163         if (!hn) {
164                 r = read_etc_hostname(NULL, &b);
165                 if (r < 0) {
166                         if (r == -ENOENT)
167                                 enoent = true;
168                         else
169                                 log_warning_errno(r, "Failed to read configured hostname: %m");
170                 } else {
171                         hn = b;
172                         source = HOSTNAME_STATIC;
173                 }
174         }
175 
176         if (!hn) {
177                 _cleanup_free_ char *buf = NULL;
178 
179                 /* Don't override the hostname if it is already set and not explicitly configured */
180 
181                 r = gethostname_full(GET_HOSTNAME_ALLOW_LOCALHOST, &buf);
182                 if (r == -ENOMEM)
183                         return log_oom();
184                 if (r >= 0) {
185                         log_debug("No hostname configured, leaving existing hostname <%s> in place.", buf);
186                         return 0;
187                 }
188 
189                 if (enoent)
190                         log_info("No hostname configured, using default hostname.");
191 
192                 hn = b = get_default_hostname();
193                 if (!hn)
194                         return log_oom();
195 
196                 source = HOSTNAME_DEFAULT;
197 
198         }
199 
200         r = sethostname_idempotent_full(hn, really);
201         if (r < 0)
202                 return log_warning_errno(r, "Failed to set hostname to <%s>: %m", hn);
203         if (r == 0)
204                 log_debug("Hostname was already set to <%s>.", hn);
205         else
206                 log_info("Hostname %s to <%s>.",
207                          really ? "set" : "would have been set",
208                          hn);
209 
210         if (really)
211                 hostname_update_source_hint(hn, source);
212 
213         return r;
214 }
215 
216 static const char* const hostname_source_table[] = {
217         [HOSTNAME_STATIC]    = "static",
218         [HOSTNAME_TRANSIENT] = "transient",
219         [HOSTNAME_DEFAULT]   = "default",
220 };
221 
222 DEFINE_STRING_TABLE_LOOKUP(hostname_source, HostnameSource);
223