diff --git a/TODO b/TODO
index f3cfbcbaef..d3cb7873d4 100644
--- a/TODO
+++ b/TODO
@@ -118,6 +118,10 @@ Deprecations and removals:
* Consider removing root=gpt-auto, and push people to use root=dissect instead.
+* Once
+ https://github.com/util-linux/util-linux/commit/508fb0e7ac103b68531a59db2a4473897853ab52
+ has hit the prominent distributions, revert --issue-file= hack in units/*getty*sevice.in
+
Features:
* replace bootctl's PE version check to actually use APIs from pe-binary.[ch]
diff --git a/man/rules/meson.build b/man/rules/meson.build
index b5dfdb925c..12e9fd97aa 100644
--- a/man/rules/meson.build
+++ b/man/rules/meson.build
@@ -1118,6 +1118,7 @@ manpages = [
['systemd-socket-proxyd', '8', [], ''],
['systemd-soft-reboot.service', '8', [], ''],
['systemd-ssh-generator', '8', [], ''],
+ ['systemd-ssh-issue', '1', [], ''],
['systemd-ssh-proxy', '1', [], ''],
['systemd-stdio-bridge', '1', [], ''],
['systemd-storagetm.service', '8', ['systemd-storagetm'], 'ENABLE_STORAGETM'],
diff --git a/man/systemd-ssh-issue.xml b/man/systemd-ssh-issue.xml
new file mode 100644
index 0000000000..4e88779676
--- /dev/null
+++ b/man/systemd-ssh-issue.xml
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
+ systemd-ssh-issue
+ systemd
+
+
+
+ systemd-ssh-issue
+ 1
+
+
+
+ systemd-ssh-issue
+ Generator for SSH login prompt drop-ins
+
+
+
+
+ /usr/lib/systemd/systemd-ssh-issue
+ /usr/lib/systemd/systemd-ssh-issue
+
+
+
+
+ Description
+
+ systemd-ssh-issue is a small tool that generates a
+ /run/issue.d/50-ssh-vsock.issue drop-in file in case AF_VSOCK
+ support is available in the kernel and the VM environment. The file contains brief information about how
+ to contact the local system via SSH-over-AF_VSOCK, in particular it reports the
+ system's AF_VSOCK CID. The file is typically read and displayed by agetty8 on
+ console or serial login prompts.
+
+ This tool is automatically used by
+ systemd-ssh-generator8
+ in the AF_VSOCK socket units it generates.
+
+
+
+ Options
+ The following options are understood:
+
+
+
+
+ Generates the issue file. This command has no effect if called on systems lacking
+ AF_VSOCK support.
+
+
+
+
+
+
+ Removes the issue file if it exists.
+
+
+
+
+
+
+ Changes the path to the issue file to write to/remove. If not specified, defaults to
+ /run/issue.d/50-ssh-vsock.issue. If specified as empty string or
+ - writes the issue file contents to standard output.
+
+
+
+
+
+
+
+
+
+
+ Exit status
+
+ On success, 0 is returned, a non-zero failure code
+ otherwise.
+
+
+
+ See Also
+
+ systemd1
+ systemd-ssh-generator8
+ vsock7
+ ssh1
+ sshd8
+ agetty8
+
+
+
diff --git a/src/ssh-generator/meson.build b/src/ssh-generator/meson.build
index b14ef46ff5..f281a25184 100644
--- a/src/ssh-generator/meson.build
+++ b/src/ssh-generator/meson.build
@@ -9,6 +9,10 @@ executables += [
'name' : 'systemd-ssh-proxy',
'sources' : files('ssh-proxy.c'),
},
+ libexec_template + {
+ 'name' : 'systemd-ssh-issue',
+ 'sources' : files('ssh-issue.c'),
+ },
]
if conf.get('ENABLE_SSH_PROXY_CONFIG') == 1
diff --git a/src/ssh-generator/ssh-generator.c b/src/ssh-generator/ssh-generator.c
index 3f1fd8cc30..0c7eba366a 100644
--- a/src/ssh-generator/ssh-generator.c
+++ b/src/ssh-generator/ssh-generator.c
@@ -134,6 +134,7 @@ static int write_socket_unit(
const char *unit,
const char *listen_stream,
const char *comment,
+ const char *extra,
bool with_ssh_access_target_dependency) {
int r;
@@ -172,6 +173,9 @@ static int write_socket_unit(
"PollLimitBurst=50\n",
listen_stream);
+ if (extra)
+ fputs(extra, f);
+
r = fflush_and_check(f);
if (r < 0)
return log_error_errno(r, "Failed to write %s SSH socket unit: %m", comment);
@@ -245,6 +249,8 @@ static int add_vsock_socket(
"sshd-vsock.socket",
"vsock::22",
"AF_VSOCK",
+ "ExecStartPost=-/usr/lib/systemd/systemd-ssh-issue --make-vsock\n"
+ "ExecStopPre=-/usr/lib/systemd/systemd-ssh-issue --rm-vsock\n",
/* with_ssh_access_target_dependency= */ true);
if (r < 0)
return r;
@@ -280,6 +286,7 @@ static int add_local_unix_socket(
"sshd-unix-local.socket",
"/run/ssh-unix-local/socket",
"AF_UNIX Local",
+ /* extra= */ NULL,
/* with_ssh_access_target_dependency= */ false);
if (r < 0)
return r;
@@ -336,6 +343,7 @@ static int add_export_unix_socket(
"sshd-unix-export.socket",
"/run/host/unix-export/ssh",
"AF_UNIX Export",
+ /* extra= */ NULL,
/* with_ssh_access_target_dependency= */ true);
if (r < 0)
return r;
@@ -387,6 +395,7 @@ static int add_extra_sockets(
socket ?: "sshd-extra.socket",
*i,
*i,
+ /* extra= */ NULL,
/* with_ssh_access_target_dependency= */ true);
if (r < 0)
return r;
diff --git a/src/ssh-generator/ssh-issue.c b/src/ssh-generator/ssh-issue.c
new file mode 100644
index 0000000000..68ad66bfd0
--- /dev/null
+++ b/src/ssh-generator/ssh-issue.c
@@ -0,0 +1,242 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include
+#include
+#include
+
+#include "alloc-util.h"
+#include "ansi-color.h"
+#include "build.h"
+#include "errno-util.h"
+#include "fd-util.h"
+#include "fs-util.h"
+#include "log.h"
+#include "main-func.h"
+#include "mkdir.h"
+#include "parse-argument.h"
+#include "pretty-print.h"
+#include "socket-util.h"
+#include "string-util.h"
+#include "tmpfile-util.h"
+#include "virt.h"
+
+static enum {
+ ACTION_MAKE_VSOCK,
+ ACTION_RM_VSOCK,
+} arg_action = ACTION_MAKE_VSOCK;
+
+static char* arg_issue_path = NULL;
+static bool arg_issue_stdout = false;
+
+STATIC_DESTRUCTOR_REGISTER(arg_issue_path, freep);
+
+static int help(void) {
+ _cleanup_free_ char *link = NULL;
+ int r;
+
+ r = terminal_urlify_man("systemd-ssh-issue", "1", &link);
+ if (r < 0)
+ return log_oom();
+
+ printf("%s [OPTIONS...] --make-vsock\n"
+ "%s [OPTIONS...] --rm-vsock\n"
+ "\n%sCreate ssh /run/issue.d/ file reporting VSOCK address.%s\n\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n"
+ " --issue-path=PATH Change path to /run/issue.d/50-ssh-vsock.issue\n"
+ "\nSee the %s for details.\n",
+ program_invocation_short_name,
+ program_invocation_short_name,
+ ansi_highlight(),
+ ansi_normal(),
+ link);
+
+ return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+
+ enum {
+ ARG_MAKE_VSOCK = 0x100,
+ ARG_RM_VSOCK,
+ ARG_ISSUE_PATH,
+ ARG_VERSION,
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "make-vsock", no_argument, NULL, ARG_MAKE_VSOCK },
+ { "rm-vsock", no_argument, NULL, ARG_RM_VSOCK },
+ { "issue-path", required_argument, NULL, ARG_ISSUE_PATH },
+ {}
+ };
+
+ int c, r;
+
+ assert(argc >= 0);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
+
+ switch (c) {
+
+ case 'h':
+ return help();
+
+ case ARG_VERSION:
+ return version();
+
+ case ARG_MAKE_VSOCK:
+ arg_action = ACTION_MAKE_VSOCK;
+ break;
+
+ case ARG_RM_VSOCK:
+ arg_action = ACTION_RM_VSOCK;
+ break;
+
+ case ARG_ISSUE_PATH:
+ if (isempty(optarg) || streq(optarg, "-")) {
+ arg_issue_path = mfree(arg_issue_path);
+ arg_issue_stdout = true;
+ break;
+ }
+
+ r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_issue_path);
+ if (r < 0)
+ return r;
+
+ arg_issue_stdout = false;
+ break;
+ }
+ }
+
+ if (!arg_issue_path && !arg_issue_stdout) {
+ arg_issue_path = strdup("/run/issue.d/50-ssh-vsock.issue");
+ if (!arg_issue_path)
+ return log_oom();
+ }
+
+ return 1;
+}
+
+static int acquire_cid(unsigned *ret_cid) {
+ int r;
+
+ assert(ret_cid);
+
+ Virtualization v = detect_virtualization();
+ if (v < 0)
+ return log_error_errno(v, "Failed to detect if we run in a VM: %m");
+ if (!VIRTUALIZATION_IS_VM(v)) {
+ /* NB: if we are running in a container inside a VM, then we'll *not* do AF_VSOCK stuff */
+ log_debug("Not running in a VM, not creating issue file.");
+ *ret_cid = 0;
+ return 0;
+ }
+
+ _cleanup_close_ int vsock_fd = socket(AF_VSOCK, SOCK_STREAM|SOCK_CLOEXEC, 0);
+ if (vsock_fd < 0) {
+ if (ERRNO_IS_NOT_SUPPORTED(errno)) {
+ log_debug("Not creating issue file, since AF_VSOCK is not available.");
+ *ret_cid = 0;
+ return 0;
+ }
+
+ return log_error_errno(errno, "Unable to test if AF_VSOCK is available: %m");
+ }
+
+ vsock_fd = safe_close(vsock_fd);
+
+ unsigned local_cid;
+ r = vsock_get_local_cid(&local_cid);
+ if (r < 0) {
+ if (ERRNO_IS_DEVICE_ABSENT(r)) {
+ log_debug("Not creating issue file, since /dev/vsock is not available (even though AF_VSOCK is).");
+ *ret_cid = 0;
+ return 0;
+ }
+
+ return log_error_errno(r, "Failed to query local AF_VSOCK CID: %m");
+ }
+
+ *ret_cid = local_cid;
+ return 1;
+}
+
+static int run(int argc, char* argv[]) {
+ int r;
+
+ log_setup();
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ return r;
+
+ switch (arg_action) {
+ case ACTION_MAKE_VSOCK: {
+ unsigned cid;
+
+ r = acquire_cid(&cid);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ log_debug("Not running in a VSOCK enabled VM, skipping.");
+ break;
+ }
+
+ _cleanup_(unlink_and_freep) char *t = NULL;
+ _cleanup_(fclosep) FILE *f = NULL;
+ FILE *out;
+
+ if (arg_issue_path) {
+ r = mkdir_parents(arg_issue_path, 0755);
+ if (r < 0)
+ return log_error_errno(r, "Failed to create parent directories of '%s': %m", arg_issue_path);
+
+ r = fopen_tmpfile_linkable(arg_issue_path, O_WRONLY|O_CLOEXEC, &t, &f);
+ if (r < 0)
+ return log_error_errno(r, "Failed to create '%s': %m", arg_issue_path);
+
+ out = f;
+ } else
+ out = stdout;
+
+ fprintf(out,
+ "Try contacting this VM's SSH server via 'ssh vsock%%%u' from host.\n"
+ "\n", cid);
+
+ if (f) {
+ if (fchmod(fileno(f), 0644) < 0)
+ return log_error_errno(errno, "Failed to adjust access mode of '%s': %m", arg_issue_path);
+
+ r = flink_tmpfile(f, t, arg_issue_path, LINK_TMPFILE_REPLACE);
+ if (r < 0)
+ return log_error_errno(r, "Failed to move '%s' into place: %m", arg_issue_path);
+ }
+
+ break;
+ }
+
+ case ACTION_RM_VSOCK:
+ if (arg_issue_path) {
+ if (unlink(arg_issue_path) < 0) {
+ if (errno != ENOENT)
+ return log_error_errno(errno, "Failed to remove '%s': %m", arg_issue_path);
+
+ log_debug_errno(errno, "File '%s' does not exist, no operation executed.", arg_issue_path);
+ } else
+ log_debug("Successfully removed '%s'.", arg_issue_path);
+ } else
+ log_notice("STDOUT selected for issue file, not removing.");
+
+ break;
+
+ default:
+ assert_not_reached();
+ }
+
+ return 0;
+}
+
+DEFINE_MAIN_FUNCTION(run);
diff --git a/units/console-getty.service.in b/units/console-getty.service.in
index 2182109009..967d8337ab 100644
--- a/units/console-getty.service.in
+++ b/units/console-getty.service.in
@@ -20,7 +20,7 @@ Before=getty.target
ConditionPathExists=/dev/console
[Service]
-ExecStart=-/sbin/agetty --noreset --noclear --keep-baud 115200,57600,38400,9600 - ${TERM}
+ExecStart=-/sbin/agetty --noreset --noclear --issue-file=/etc/issue:/etc/issue.d:/run/issue.d:/usr/lib/issue.d --keep-baud 115200,57600,38400,9600 - ${TERM}
Type=idle
Restart=always
UtmpIdentifier=cons
diff --git a/units/container-getty@.service.in b/units/container-getty@.service.in
index f24bb73fc1..e0b27613df 100644
--- a/units/container-getty@.service.in
+++ b/units/container-getty@.service.in
@@ -25,7 +25,7 @@ Conflicts=rescue.service
Before=rescue.service
[Service]
-ExecStart=-/sbin/agetty --noreset --noclear - ${TERM}
+ExecStart=-/sbin/agetty --noreset --noclear --issue-file=/etc/issue:/etc/issue.d:/run/issue.d:/usr/lib/issue.d - ${TERM}
Type=idle
Restart=always
RestartSec=0
diff --git a/units/getty@.service.in b/units/getty@.service.in
index a43e01144f..104c4acc96 100644
--- a/units/getty@.service.in
+++ b/units/getty@.service.in
@@ -34,7 +34,7 @@ Before=rescue.service
ConditionPathExists=/dev/tty0
[Service]
-ExecStart=-/sbin/agetty --noreset --noclear - ${TERM}
+ExecStart=-/sbin/agetty --noreset --noclear --issue-file=/etc/issue:/etc/issue.d:/run/issue.d:/usr/lib/issue.d - ${TERM}
Type=idle
Restart=always
RestartSec=0
diff --git a/units/serial-getty@.service.in b/units/serial-getty@.service.in
index c0adaf7bdb..0134c83d48 100644
--- a/units/serial-getty@.service.in
+++ b/units/serial-getty@.service.in
@@ -30,7 +30,7 @@ Conflicts=rescue.service
Before=rescue.service
[Service]
-ExecStart=-/sbin/agetty --noreset --noclear --keep-baud 115200,57600,38400,9600 - ${TERM}
+ExecStart=-/sbin/agetty --noreset --noclear --issue-file=/etc/issue:/etc/issue.d:/run/issue.d:/usr/lib/issue.d --keep-baud 115200,57600,38400,9600 - ${TERM}
Type=idle
Restart=always
UtmpIdentifier=%I