]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/ssh-generator/ssh-generator.c
path-lookup: rename lookup_paths_free -> _done
[thirdparty/systemd.git] / src / ssh-generator / ssh-generator.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <fcntl.h>
4 #include <sys/ioctl.h>
5 #include <unistd.h>
6
7 #include "creds-util.h"
8 #include "fd-util.h"
9 #include "fileio.h"
10 #include "generator.h"
11 #include "install.h"
12 #include "missing_socket.h"
13 #include "parse-util.h"
14 #include "path-util.h"
15 #include "proc-cmdline.h"
16 #include "socket-netlink.h"
17 #include "socket-util.h"
18 #include "special.h"
19 #include "virt.h"
20
21 /* A small generator binding potentially five or more SSH sockets:
22 *
23 * 1. Listen on AF_VSOCK port 22 if we run in a VM with AF_VSOCK enabled
24 * 2. Listen on AF_UNIX socket /run/host/unix-export/ssh if we run in a container with /run/host/ support
25 * 3. Listen on AF_UNIX socket /run/ssh-unix-local/socket (always)
26 * 4. Listen on any socket specified via kernel command line option systemd.ssh_listen=
27 * 5. Similar, but from system credential ssh.listen
28 *
29 * The first two provide a nice way for hosts to connect to containers and VMs they invoke via the usual SSH
30 * logic, but without waiting for networking or suchlike. The third allows the same for local clients. */
31
32 static const char *arg_dest = NULL;
33 static bool arg_auto = true;
34 static char **arg_listen_extra = NULL;
35
36 static int parse_proc_cmdline_item(const char *key, const char *value, void *data) {
37 int r;
38
39 assert(key);
40
41 if (proc_cmdline_key_streq(key, "systemd.ssh_auto")) {
42 r = value ? parse_boolean(value) : 1;
43 if (r < 0)
44 log_warning_errno(r, "Failed to parse systemd.ssh_auto switch \"%s\", ignoring: %m", value);
45 else
46 arg_auto = r;
47
48 } else if (proc_cmdline_key_streq(key, "systemd.ssh_listen")) {
49
50 if (proc_cmdline_value_missing(key, value))
51 return 0;
52
53 SocketAddress sa;
54 r = socket_address_parse(&sa, value);
55 if (r < 0)
56 log_warning_errno(r, "Failed to parse systemd.ssh_listen= expression, ignoring: %s", value);
57 else {
58 _cleanup_free_ char *s = NULL;
59 r = socket_address_print(&sa, &s);
60 if (r < 0)
61 return log_error_errno(r, "Failed to format socket address: %m");
62
63 if (strv_consume(&arg_listen_extra, TAKE_PTR(s)) < 0)
64 return log_oom();
65 }
66 }
67
68 return 0;
69 }
70
71 static int make_sshd_template_unit(
72 const char *dest,
73 const char *template,
74 const char *sshd_binary,
75 const char *found_sshd_template_service,
76 char **generated_sshd_template_unit) {
77
78 int r;
79
80 assert(dest);
81 assert(template);
82 assert(sshd_binary);
83 assert(generated_sshd_template_unit);
84
85 /* If the system has a suitable template already, symlink it to the name we want to reuse it */
86 if (found_sshd_template_service)
87 return generator_add_symlink(
88 dest,
89 template,
90 /* dep_type= */ NULL,
91 found_sshd_template_service);
92
93 if (!*generated_sshd_template_unit) {
94 _cleanup_fclose_ FILE *f = NULL;
95
96 r = generator_open_unit_file_full(
97 dest,
98 /* source= */ NULL,
99 "sshd-generated@.service", /* Give this generated unit a generic name, since we want to use it for both AF_UNIX and AF_VSOCK */
100 &f,
101 generated_sshd_template_unit,
102 /* ret_temp_path= */ NULL);
103 if (r < 0)
104 return r;
105
106 fprintf(f,
107 "[Unit]\n"
108 "Description=OpenSSH Per-Connection Server Daemon\n"
109 "Documentation=man:systemd-ssh-generator(8) man:sshd(8)\n"
110 "[Service]\n"
111 "ExecStart=-%s -i\n"
112 "StandardInput=socket",
113 sshd_binary);
114
115 r = fflush_and_check(f);
116 if (r < 0)
117 return log_error_errno(r, "Failed to write sshd template: %m");
118 }
119
120 return generator_add_symlink(
121 dest,
122 template,
123 /* dep_type= */ NULL,
124 *generated_sshd_template_unit);
125 }
126
127 static int write_socket_unit(
128 const char *dest,
129 const char *unit,
130 const char *listen_stream,
131 const char *comment) {
132
133 int r;
134
135 assert(dest);
136 assert(unit);
137 assert(listen_stream);
138 assert(comment);
139
140 _cleanup_fclose_ FILE *f = NULL;
141 r = generator_open_unit_file(
142 dest,
143 /* source= */ NULL,
144 unit,
145 &f);
146 if (r < 0)
147 return r;
148
149 fprintf(f,
150 "[Unit]\n"
151 "Description=OpenSSH Server Socket (systemd-ssh-generator, %s)\n"
152 "Documentation=man:systemd-ssh-generator(8)\n"
153 "\n[Socket]\n"
154 "ListenStream=%s\n"
155 "Accept=yes\n"
156 "PollLimitIntervalSec=30s\n"
157 "PollLimitBurst=50\n",
158 comment,
159 listen_stream);
160
161 r = fflush_and_check(f);
162 if (r < 0)
163 return log_error_errno(r, "Failed to write %s SSH socket unit: %m", comment);
164
165 r = generator_add_symlink(
166 dest,
167 SPECIAL_SOCKETS_TARGET,
168 "wants",
169 unit);
170 if (r < 0)
171 return r;
172
173 return 0;
174 }
175
176 static int add_vsock_socket(
177 const char *dest,
178 const char *sshd_binary,
179 const char *found_sshd_template_unit,
180 char **generated_sshd_template_unit) {
181
182 int r;
183
184 assert(dest);
185 assert(generated_sshd_template_unit);
186
187 Virtualization v = detect_virtualization();
188 if (v < 0)
189 return log_error_errno(v, "Failed to detect if we run in a VM: %m");
190 if (!VIRTUALIZATION_IS_VM(v)) {
191 /* NB: if we are running in a container inside a VM, then we'll *not* do AF_VSOCK stuff */
192 log_debug("Not running in a VM, not listening on AF_VSOCK.");
193 return 0;
194 }
195
196 _cleanup_close_ int vsock_fd = socket(AF_VSOCK, SOCK_STREAM|SOCK_CLOEXEC, 0);
197 if (vsock_fd < 0) {
198 if (ERRNO_IS_NOT_SUPPORTED(errno)) {
199 log_debug("Not creating AF_VSOCK ssh listener, since AF_VSOCK is not available.");
200 return 0;
201 }
202
203 return log_error_errno(errno, "Unable to test if AF_VSOCK is available: %m");
204 }
205
206 vsock_fd = safe_close(vsock_fd);
207
208 /* Determine the local CID so that we can log it to help users to connect to this VM */
209 unsigned local_cid;
210 r = vsock_get_local_cid(&local_cid);
211 if (r < 0) {
212 if (ERRNO_IS_DEVICE_ABSENT(r)) {
213 log_debug("Not creating AF_VSOCK ssh listener, since /dev/vsock is not available (even though AF_VSOCK is).");
214 return 0;
215 }
216
217 return log_error_errno(r, "Failed to query local AF_VSOCK CID: %m");
218 }
219
220 r = make_sshd_template_unit(
221 dest,
222 "sshd-vsock@.service",
223 sshd_binary,
224 found_sshd_template_unit,
225 generated_sshd_template_unit);
226 if (r < 0)
227 return r;
228
229 r = write_socket_unit(
230 dest,
231 "sshd-vsock.socket",
232 "vsock::22",
233 "AF_VSOCK");
234 if (r < 0)
235 return r;
236
237 log_info("Binding SSH to AF_VSOCK vsock::22.\n"
238 "→ connect via 'ssh vsock/%u' from host", local_cid);
239 return 0;
240 }
241
242 static int add_local_unix_socket(
243 const char *dest,
244 const char *sshd_binary,
245 const char *found_sshd_template_unit,
246 char **generated_sshd_template_unit) {
247
248 int r;
249
250 assert(dest);
251 assert(sshd_binary);
252 assert(generated_sshd_template_unit);
253
254 r = make_sshd_template_unit(
255 dest,
256 "sshd-unix-local@.service",
257 sshd_binary,
258 found_sshd_template_unit,
259 generated_sshd_template_unit);
260 if (r < 0)
261 return r;
262
263 r = write_socket_unit(
264 dest,
265 "sshd-unix-local.socket",
266 "/run/ssh-unix-local/socket",
267 "AF_UNIX Local");
268 if (r < 0)
269 return r;
270
271
272 log_info("Binding SSH to AF_UNIX socket /run/ssh-unix-local/socket.\n"
273 "→ connect via 'ssh .host' locally");
274 return 0;
275 }
276
277 static int add_export_unix_socket(
278 const char *dest,
279 const char *sshd_binary,
280 const char *found_sshd_template_unit,
281 char **generated_sshd_template_unit) {
282
283 int r;
284
285 assert(dest);
286 assert(sshd_binary);
287 assert(generated_sshd_template_unit);
288
289 Virtualization v = detect_container();
290 if (v < 0)
291 return log_error_errno(v, "Failed to detect if we run in a container: %m");
292 if (v == VIRTUALIZATION_NONE) {
293 log_debug("Not running in container, not listening on /run/host/unix-export/ssh");
294 return 0;
295 }
296
297 if (access("/run/host/unix-export/", W_OK) < 0) {
298 if (errno == ENOENT) {
299 log_debug("Container manager does not provide /run/host/unix-export/ mount, not binding AF_UNIX socket there.");
300 return 0;
301 }
302 if (errno == EROFS || ERRNO_IS_PRIVILEGE(errno)) {
303 log_debug("Container manager does not provide write access to /run/host/unix-export/, not binding AF_UNIX socket there.");
304 return 0;
305 }
306
307 return log_error_errno(errno, "Unable to check if /run/host/unix-export exists: %m");
308 }
309
310 r = make_sshd_template_unit(
311 dest,
312 "sshd-unix-export@.service",
313 sshd_binary,
314 found_sshd_template_unit,
315 generated_sshd_template_unit);
316 if (r < 0)
317 return r;
318
319 r = write_socket_unit(
320 dest,
321 "sshd-unix-export.socket",
322 "/run/host/unix-export/ssh",
323 "AF_UNIX Export");
324 if (r < 0)
325 return r;
326
327 log_info("Binding SSH to AF_UNIX socket /run/host/unix-export/ssh\n"
328 "→ connect via 'ssh unix/run/systemd/nspawn/unix-export/\?\?\?/ssh' from host");
329
330 return 0;
331 }
332
333 static int add_extra_sockets(
334 const char *dest,
335 const char *sshd_binary,
336 const char *found_sshd_template_unit,
337 char **generated_sshd_template_unit) {
338
339 unsigned n = 1;
340 int r;
341
342 assert(dest);
343 assert(sshd_binary);
344 assert(generated_sshd_template_unit);
345
346 if (strv_isempty(arg_listen_extra))
347 return 0;
348
349 STRV_FOREACH(i, arg_listen_extra) {
350 _cleanup_free_ char *service = NULL, *socket = NULL;
351
352 if (n > 1) {
353 if (asprintf(&service, "sshd-extra-%u@.service", n) < 0)
354 return log_oom();
355
356 if (asprintf(&socket, "sshd-extra-%u.socket", n) < 0)
357 return log_oom();
358 }
359
360 r = make_sshd_template_unit(
361 dest,
362 service ?: "sshd-extra@.service",
363 sshd_binary,
364 found_sshd_template_unit,
365 generated_sshd_template_unit);
366 if (r < 0)
367 return r;
368
369 r = write_socket_unit(
370 dest,
371 socket ?: "sshd-extra.socket",
372 *i,
373 *i);
374 if (r < 0)
375 return r;
376
377 log_info("Binding SSH to socket %s.", *i);
378 n++;
379 }
380
381 return 0;
382 }
383
384 static int parse_credentials(void) {
385 _cleanup_free_ char *b = NULL;
386 size_t sz = 0;
387 int r;
388
389 r = read_credential_with_decryption("ssh.listen", (void*) &b, &sz);
390 if (r < 0)
391 return r;
392 if (r == 0)
393 return 0;
394
395 _cleanup_fclose_ FILE *f = NULL;
396 f = fmemopen_unlocked(b, sz, "r");
397 if (!f)
398 return log_oom();
399
400 for (;;) {
401 _cleanup_free_ char *item = NULL;
402
403 r = read_stripped_line(f, LINE_MAX, &item);
404 if (r == 0)
405 break;
406 if (r < 0) {
407 log_error_errno(r, "Failed to parse credential 'ssh.listen': %m");
408 break;
409 }
410
411 if (startswith(item, "#"))
412 continue;
413
414 SocketAddress sa;
415 r = socket_address_parse(&sa, item);
416 if (r < 0) {
417 log_warning_errno(r, "Failed to parse systemd.ssh_listen= expression, ignoring: %s", item);
418 continue;
419 }
420
421 _cleanup_free_ char *s = NULL;
422 r = socket_address_print(&sa, &s);
423 if (r < 0)
424 return log_error_errno(r, "Failed to format socket address: %m");
425
426 if (strv_consume(&arg_listen_extra, TAKE_PTR(s)) < 0)
427 return log_oom();
428 }
429
430 return 0;
431 }
432
433 static int run(const char *dest, const char *dest_early, const char *dest_late) {
434 int r;
435
436 assert_se(arg_dest = dest);
437
438 r = proc_cmdline_parse(parse_proc_cmdline_item, /* userdata= */ NULL, /* flags= */ 0);
439 if (r < 0)
440 log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m");
441
442 (void) parse_credentials();
443
444 strv_sort(arg_listen_extra);
445 strv_uniq(arg_listen_extra);
446
447 if (!arg_auto && strv_isempty(arg_listen_extra)) {
448 log_debug("Disabling SSH generator logic, because as it has been turned off explicitly.");
449 return 0;
450 }
451
452 _cleanup_free_ char *sshd_binary = NULL;
453 r = find_executable("sshd", &sshd_binary);
454 if (r == -ENOENT) {
455 log_info("Disabling SSH generator logic, since sshd is not installed.");
456 return 0;
457 }
458 if (r < 0)
459 return log_error_errno(r, "Failed to determine if sshd is installed: %m");
460
461 _cleanup_(lookup_paths_done) LookupPaths lp = {};
462 r = lookup_paths_init_or_warn(&lp, RUNTIME_SCOPE_SYSTEM, LOOKUP_PATHS_EXCLUDE_GENERATED, /* root_dir= */ NULL);
463 if (r < 0)
464 return r;
465
466 _cleanup_free_ char *found_sshd_template_unit = NULL;
467 r = unit_file_exists_full(RUNTIME_SCOPE_SYSTEM, &lp, "sshd@.service", &found_sshd_template_unit);
468 if (r < 0)
469 return log_error_errno(r, "Unable to detect if sshd@.service exists: %m");
470
471 _cleanup_free_ char *generated_sshd_template_unit = NULL;
472 RET_GATHER(r, add_extra_sockets(dest, sshd_binary, found_sshd_template_unit, &generated_sshd_template_unit));
473
474 if (arg_auto) {
475 RET_GATHER(r, add_vsock_socket(dest, sshd_binary, found_sshd_template_unit, &generated_sshd_template_unit));
476 RET_GATHER(r, add_local_unix_socket(dest, sshd_binary, found_sshd_template_unit, &generated_sshd_template_unit));
477 RET_GATHER(r, add_export_unix_socket(dest, sshd_binary, found_sshd_template_unit, &generated_sshd_template_unit));
478 }
479
480 return r;
481 }
482
483 DEFINE_MAIN_GENERATOR_FUNCTION(run);