1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
7 #include "creds-util.h"
10 #include "generator.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"
21 /* A small generator binding potentially five or more SSH sockets:
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
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. */
32 static const char *arg_dest
= NULL
;
33 static bool arg_auto
= true;
34 static char **arg_listen_extra
= NULL
;
36 static int parse_proc_cmdline_item(const char *key
, const char *value
, void *data
) {
41 if (proc_cmdline_key_streq(key
, "systemd.ssh_auto")) {
42 r
= value
? parse_boolean(value
) : 1;
44 log_warning_errno(r
, "Failed to parse systemd.ssh_auto switch \"%s\", ignoring: %m", value
);
48 } else if (proc_cmdline_key_streq(key
, "systemd.ssh_listen")) {
50 if (proc_cmdline_value_missing(key
, value
))
54 r
= socket_address_parse(&sa
, value
);
56 log_warning_errno(r
, "Failed to parse systemd.ssh_listen= expression, ignoring: %s", value
);
58 _cleanup_free_
char *s
= NULL
;
59 r
= socket_address_print(&sa
, &s
);
61 return log_error_errno(r
, "Failed to format socket address: %m");
63 if (strv_consume(&arg_listen_extra
, TAKE_PTR(s
)) < 0)
71 static int make_sshd_template_unit(
74 const char *sshd_binary
,
75 const char *found_sshd_template_service
,
76 char **generated_sshd_template_unit
) {
83 assert(generated_sshd_template_unit
);
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(
91 found_sshd_template_service
);
93 if (!*generated_sshd_template_unit
) {
94 _cleanup_fclose_
FILE *f
= NULL
;
96 r
= generator_open_unit_file_full(
99 "sshd-generated@.service", /* Give this generated unit a generic name, since we want to use it for both AF_UNIX and AF_VSOCK */
101 generated_sshd_template_unit
,
102 /* ret_temp_path= */ NULL
);
108 "Description=OpenSSH Per-Connection Server Daemon\n"
109 "Documentation=man:systemd-ssh-generator(8) man:sshd(8)\n"
112 "StandardInput=socket",
115 r
= fflush_and_check(f
);
117 return log_error_errno(r
, "Failed to write sshd template: %m");
120 return generator_add_symlink(
123 /* dep_type= */ NULL
,
124 *generated_sshd_template_unit
);
127 static int write_socket_unit(
130 const char *listen_stream
,
131 const char *comment
) {
137 assert(listen_stream
);
140 _cleanup_fclose_
FILE *f
= NULL
;
141 r
= generator_open_unit_file(
151 "Description=OpenSSH Server Socket (systemd-ssh-generator, %s)\n"
152 "Documentation=man:systemd-ssh-generator(8)\n"
156 "PollLimitIntervalSec=30s\n"
157 "PollLimitBurst=50\n",
161 r
= fflush_and_check(f
);
163 return log_error_errno(r
, "Failed to write %s SSH socket unit: %m", comment
);
165 r
= generator_add_symlink(
167 SPECIAL_SOCKETS_TARGET
,
176 static int add_vsock_socket(
178 const char *sshd_binary
,
179 const char *found_sshd_template_unit
,
180 char **generated_sshd_template_unit
) {
185 assert(generated_sshd_template_unit
);
187 Virtualization v
= detect_virtualization();
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.");
196 _cleanup_close_
int vsock_fd
= socket(AF_VSOCK
, SOCK_STREAM
|SOCK_CLOEXEC
, 0);
198 if (ERRNO_IS_NOT_SUPPORTED(errno
)) {
199 log_debug("Not creating AF_VSOCK ssh listener, since AF_VSOCK is not available.");
203 return log_error_errno(errno
, "Unable to test if AF_VSOCK is available: %m");
206 vsock_fd
= safe_close(vsock_fd
);
208 /* Determine the local CID so that we can log it to help users to connect to this VM */
210 r
= vsock_get_local_cid(&local_cid
);
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).");
217 return log_error_errno(r
, "Failed to query local AF_VSOCK CID: %m");
220 r
= make_sshd_template_unit(
222 "sshd-vsock@.service",
224 found_sshd_template_unit
,
225 generated_sshd_template_unit
);
229 r
= write_socket_unit(
237 log_info("Binding SSH to AF_VSOCK vsock::22.\n"
238 "→ connect via 'ssh vsock/%u' from host", local_cid
);
242 static int add_local_unix_socket(
244 const char *sshd_binary
,
245 const char *found_sshd_template_unit
,
246 char **generated_sshd_template_unit
) {
252 assert(generated_sshd_template_unit
);
254 r
= make_sshd_template_unit(
256 "sshd-unix-local@.service",
258 found_sshd_template_unit
,
259 generated_sshd_template_unit
);
263 r
= write_socket_unit(
265 "sshd-unix-local.socket",
266 "/run/ssh-unix-local/socket",
272 log_info("Binding SSH to AF_UNIX socket /run/ssh-unix-local/socket.\n"
273 "→ connect via 'ssh .host' locally");
277 static int add_export_unix_socket(
279 const char *sshd_binary
,
280 const char *found_sshd_template_unit
,
281 char **generated_sshd_template_unit
) {
287 assert(generated_sshd_template_unit
);
289 Virtualization v
= detect_container();
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");
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.");
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.");
307 return log_error_errno(errno
, "Unable to check if /run/host/unix-export exists: %m");
310 r
= make_sshd_template_unit(
312 "sshd-unix-export@.service",
314 found_sshd_template_unit
,
315 generated_sshd_template_unit
);
319 r
= write_socket_unit(
321 "sshd-unix-export.socket",
322 "/run/host/unix-export/ssh",
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");
333 static int add_extra_sockets(
335 const char *sshd_binary
,
336 const char *found_sshd_template_unit
,
337 char **generated_sshd_template_unit
) {
344 assert(generated_sshd_template_unit
);
346 if (strv_isempty(arg_listen_extra
))
349 STRV_FOREACH(i
, arg_listen_extra
) {
350 _cleanup_free_
char *service
= NULL
, *socket
= NULL
;
353 if (asprintf(&service
, "sshd-extra-%u@.service", n
) < 0)
356 if (asprintf(&socket
, "sshd-extra-%u.socket", n
) < 0)
360 r
= make_sshd_template_unit(
362 service
?: "sshd-extra@.service",
364 found_sshd_template_unit
,
365 generated_sshd_template_unit
);
369 r
= write_socket_unit(
371 socket
?: "sshd-extra.socket",
377 log_info("Binding SSH to socket %s.", *i
);
384 static int parse_credentials(void) {
385 _cleanup_free_
char *b
= NULL
;
389 r
= read_credential_with_decryption("ssh.listen", (void*) &b
, &sz
);
395 _cleanup_fclose_
FILE *f
= NULL
;
396 f
= fmemopen_unlocked(b
, sz
, "r");
401 _cleanup_free_
char *item
= NULL
;
403 r
= read_stripped_line(f
, LINE_MAX
, &item
);
407 log_error_errno(r
, "Failed to parse credential 'ssh.listen': %m");
411 if (startswith(item
, "#"))
415 r
= socket_address_parse(&sa
, item
);
417 log_warning_errno(r
, "Failed to parse systemd.ssh_listen= expression, ignoring: %s", item
);
421 _cleanup_free_
char *s
= NULL
;
422 r
= socket_address_print(&sa
, &s
);
424 return log_error_errno(r
, "Failed to format socket address: %m");
426 if (strv_consume(&arg_listen_extra
, TAKE_PTR(s
)) < 0)
433 static int run(const char *dest
, const char *dest_early
, const char *dest_late
) {
436 assert_se(arg_dest
= dest
);
438 r
= proc_cmdline_parse(parse_proc_cmdline_item
, /* userdata= */ NULL
, /* flags= */ 0);
440 log_warning_errno(r
, "Failed to parse kernel command line, ignoring: %m");
442 (void) parse_credentials();
444 strv_sort(arg_listen_extra
);
445 strv_uniq(arg_listen_extra
);
447 if (!arg_auto
&& strv_isempty(arg_listen_extra
)) {
448 log_debug("Disabling SSH generator logic, because as it has been turned off explicitly.");
452 _cleanup_free_
char *sshd_binary
= NULL
;
453 r
= find_executable("sshd", &sshd_binary
);
455 log_info("Disabling SSH generator logic, since sshd is not installed.");
459 return log_error_errno(r
, "Failed to determine if sshd is installed: %m");
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
);
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
);
469 return log_error_errno(r
, "Unable to detect if sshd@.service exists: %m");
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
));
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
));
483 DEFINE_MAIN_GENERATOR_FUNCTION(run
);