Issue OSC ANSI sequence whenever we change "context" of a TTY, i.e. acquire privs, enter container or VM or similar (#35224)
This is mostly a strawman to get a discussion going regarding how to communicate to terminal emulators such as ptyxis about run0 (and nspawn, and vmspawn, and moe) and what it does. It's hierarchical and I think still relatively simple. /cc @chergert
This commit is contained in:
@@ -0,0 +1,339 @@
|
||||
---
|
||||
title: OSC 3008: Hierarchical Context Signalling
|
||||
category: Interfaces
|
||||
layout: default
|
||||
SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
---
|
||||
|
||||
# OSC 3008: Hierarchical Context Signalling
|
||||
|
||||
A terminal connects a user with programs. Control of the program side of
|
||||
terminals is typically passed around to various different components while the
|
||||
user is active: a shell might pass control to a process it invokes. If that
|
||||
process is `run0` then primary control is passed to the privileged session of
|
||||
the target user. If `systemd-nspawn` is invoked to start a container, primary
|
||||
control is passed to that container, and so on.
|
||||
|
||||
A terminal emulator might be interested to know which component is currently in
|
||||
primary control of the program side of a terminal. OSC 3008 is a mechanism to
|
||||
inform it about such contexts. Each component taking over control can inform
|
||||
the terminal emulators that a new context begins now, and then use the terminal
|
||||
or pass control down to further apps, which can introduce contexts. Each
|
||||
context may carry various descriptive metadata fields.
|
||||
|
||||
## Status
|
||||
|
||||
This OSC sequence has been invented by the systemd project and is generated by
|
||||
systemd. Currently, no terminal application is known that consumes these
|
||||
sequences.
|
||||
|
||||
## Use Cases
|
||||
|
||||
Terminal emulators can use hierarchical context information:
|
||||
|
||||
1. To introduce markers/bookmarks in the output that the user can jump between.
|
||||
|
||||
2. To visually identify output from different contexts. For example the
|
||||
background of the associated output can be tinted in a reddish tone when
|
||||
privileges are acquired, and similar.
|
||||
|
||||
3. Meta information on specific output can be shown in a tooltip or similar
|
||||
|
||||
4. Programs (and all subcontexts) can be killed via a right-click menu on the
|
||||
output they generate.
|
||||
|
||||
5. Similar, a right-click menu might offer an item to offer opening a new
|
||||
interactive shell in the same working directory that was current on the
|
||||
selected context.
|
||||
|
||||
6. Failed commands or aborted sessions can be marked requesting user attention.
|
||||
|
||||
## Context Types
|
||||
|
||||
There are various types of contexts defined by this specification:
|
||||
|
||||
1. `boot` → a booted system initiates this context early at boot. (systemd's
|
||||
PID 1 generates this on `/dev/console`.)
|
||||
|
||||
2. `container` → a container manager initialized an interactive connection to a
|
||||
container. (`systemd-nspawn` generates this when interactively invoking a
|
||||
container. `machinectl login`, `machinectl shell` do this too.)
|
||||
|
||||
3. `vm` → a VM manager initialized a terminal connection to a
|
||||
VM. (`systemd-vmspawn` generates this when interactively invoking a VM, as
|
||||
one example.)
|
||||
|
||||
4. `elevate` → when the user interactively acquired higher privileges. (`run0`
|
||||
initiates a context of this type whenever the user invokes it to acquire
|
||||
root privileges.)
|
||||
|
||||
5. `chpriv` → similar, but when the user acquired *different* privileges, not
|
||||
necessarily higher ones. (`run0` initiates a context of this type whenever
|
||||
the user invokes it to acquire non-root privileges of another user.)
|
||||
|
||||
5. `subcontext` → similar, but the source and target privileges where
|
||||
identical. (`run0` initiates a context of this type whenever the user
|
||||
invokes it to acquire privileges of the user itself.)
|
||||
|
||||
6. `remote` → a user invoked a tool such as `ssh` to connect to a remote
|
||||
system.
|
||||
|
||||
7. `shell` → an interactive terminal shell initiates this context
|
||||
|
||||
8. `command` → a shell interactively invokes a new program.
|
||||
|
||||
9. `app` → an interactive program may initiate this context.
|
||||
|
||||
10. `service` → the service manager invokes an interactive service on the terminal
|
||||
|
||||
11. `session` → a login session of the user is initialized.
|
||||
|
||||
## Semantics
|
||||
|
||||
Contexts in the sense of OSC 3008 are hierarchical, and describe a tree
|
||||
structure: whenever a new context is opened it becomes the new active context,
|
||||
and the previously active context becomes its parent (if there is one). Only
|
||||
one context is currently active, but previously opened contexts remain valid in
|
||||
the background. Any other data written or read should be considered associated
|
||||
with the currently active context.
|
||||
|
||||
Each context carries an identifier, chosen by the component opening the
|
||||
context. The identifier can chosen freely, but must not be longer than 64
|
||||
characters. The characters may be in the 32…126 byte range. Identifiers should
|
||||
be universally unique, for example randomly generated. A freshly generated UUID
|
||||
would work well for this, but this could also be something like the Linux boot
|
||||
ID combined with the 64bit inode number of Linux pidfds, or something hashed
|
||||
from it.
|
||||
|
||||
Fundamentally, there are two OSC 3008 commands defined:
|
||||
|
||||
1. OSC "`3008;start=`" … (the *start sequence*) → this initiates, updates or
|
||||
indicates a return to a context. It carries a context identifier, and
|
||||
typically some metadata. This may be sent to first initiate a context. If
|
||||
sent again for the same context ID that was initiated already this indicates
|
||||
an update of the existing context. In this case, *any* previously set
|
||||
metadata fields for the context are flushed out, reset to their defaults,
|
||||
and then reinitialized from the newly supplied data. Also, in this case any
|
||||
subcontexts of the contexts are implicitly terminated.
|
||||
|
||||
2. OSC "`3008;end=`" … (the *end sequence*) → this terminates a context. It
|
||||
carries a context identifier to close, initiated before with OSC
|
||||
"`3008;start=`". It may also carry additional metadata.
|
||||
|
||||
## General Syntax
|
||||
|
||||
This builds on ECMA-48, and reuses the OSC and ST concepts introduced there.
|
||||
|
||||
For sequences following this specification it is recommended to encode OSC as
|
||||
0x1B 0x5D, and ST as 0x1B 0x5C.
|
||||
|
||||
ECMA-48 only allows characters from the range 0x20…0x7e (i.e. 32…126) inside
|
||||
OSC sequences. However, most terminal emulators nowadays allow the ASCII byte
|
||||
range > 0x7f in the OSC sequences they process, and so does this
|
||||
specification. Control characters (< 0x20 and 0x7f) are not allowed. The
|
||||
semicolon character ("`;`") – which is used as field separator by this
|
||||
specification – shall be replaced by "`\x3b`" and the backslash character
|
||||
("`\`") shall be replaced by "`\x5c`". All textual fields must be encoded in
|
||||
UTF-8, and then escaped with these two replacements.
|
||||
|
||||
The start sequence begins with OSC, followed by the string `3008;start=`,
|
||||
followed by the context ID. This is then followed by any number of metadata
|
||||
fields, including none. Metadata fields begin with a semicolon (`;`) followed
|
||||
by in a string identifying the type of field, followed by an equal sign (`=`),
|
||||
and the field value. The sequence ends in ST.
|
||||
|
||||
The end sequence begins with OSC, followed by the string `3008;end=`, followed
|
||||
by the context ID, and a series of metadata fields in the same syntax as for
|
||||
the start sequence. The sequence ends in ST.
|
||||
|
||||
## Metadata Fields
|
||||
|
||||
The following fields are currently defined for the start sequence:
|
||||
|
||||
| Field | Context Types | Description |
|
||||
|---------------|---------------|-------------------------------------------------------------------------------------------------------------|
|
||||
| `type=` | *all* | Declares the context type, one of the types described above |
|
||||
| `user=` | *all* | UNIX user name the process issuing the sequence runs as |
|
||||
| `hostname=` | *all* | UNIX host name of the system the process issuing the sequence runs on |
|
||||
| `machineid=` | *all* | The machine ID (i.e. `/etc/machine-id`) of the system the process issuing the sequence runs on |
|
||||
| `bootid=` | *all* | The boot ID (i.e. `/proc/sys/kernel/random/boot_id`) of the system the process issuing the sequence runs on |
|
||||
| `pid=` | *all* | The numeric PID of the process issuing the sequence, in decimal notation |
|
||||
| `pidfdid=` | *all* | The 64bit inode number of the pidfd of the process issuing the sequence, in decimal notation |
|
||||
| `comm=` | *all* | The process name (i.e. `/proc/$PID/comm`, `PR_GET_NAME`) of the process issuing the sequence |
|
||||
| `cwd=` | `shell`, `command` | The current working directory |
|
||||
| `cmdline=` | `command` | The full command line of the invoked command |
|
||||
| `vm=` | `vm` | The name of the VM being invoked |
|
||||
| `container=` | `container` | The name of the container being invoked |
|
||||
| `targetuser=` | `elevate`, `chpriv`, `vm`, `container`, `remote`, `session` | Target UNIX user name |
|
||||
| `targethost=` | `remote` | Target UNIX, DNS host name, or IP address |
|
||||
| `sessionid=` | `session` | New allocated session ID |
|
||||
|
||||
The following fields are currently defined for the end sequence:
|
||||
|
||||
| Field | Context Types | Description |
|
||||
|---------------|---------------|-------------------------------------------------------------------------------------------------------------|
|
||||
| `exit=` | `command` | One of `success`, `failure`, `crash`, `interrupt`, indicating how the program terminated |
|
||||
| `status=` | `command` | The command's numeric exit status, i.e. the 0…255 value a program returns |
|
||||
| `signal=` | `command` | The termination signal of the command, if it died abnormally. A symbolic signal name. (`SIGKILL`, …) |
|
||||
|
||||
All fields are optional, including the context type. However, it is generally
|
||||
recommended to always include the first 7 fields listed above, to make it easy
|
||||
to pinpoint the origin of a context in a race-free fashion, without any
|
||||
ambiguities.
|
||||
|
||||
The order of the metadata fields is undefined, they may appear in any order
|
||||
(including that `type=` is specified at the very end or in the middle!). Note
|
||||
that `start=` and `end=` are not considered metadata fields but part of the
|
||||
start sequence, and hence must always appear right after OSC.
|
||||
|
||||
## Processing, Limits, Security
|
||||
|
||||
All context information provided like this should be considered auxiliary and –
|
||||
to some degree – redundant information. Hence, it would be wise for a terminal
|
||||
to enforce limits on various resources, dropping additional data once these
|
||||
limits are hit. Most importantly, a maximum stacking depth should probably
|
||||
enforced: any attempts to initiate further contexts should be ignored once the
|
||||
stack limit is hit (i.e. the earlier contexts should be kept, the later
|
||||
contexts be discarded, not the opposite). Overly long fields should be
|
||||
discarded (or potentially truncated, depending on the field type). This
|
||||
specification does not recommend any specific stack or string limits for now.
|
||||
|
||||
The usual terminal reset sequences should *not* effect the stack of contexts
|
||||
(this is a safety feature: a program down the stack should not be able to
|
||||
affect the stack further up, possibly hiding relevant information). A temporary
|
||||
TTY hangup (`vhangup()`) should result in a full reset of the stack.
|
||||
|
||||
All provided data should be processed in a lenient, graceful fashion: if a
|
||||
sequence contains invalid fields, those fields should be ignored, but the rest
|
||||
of the fields should still be used. In particular, unknown fields should be
|
||||
ignored.
|
||||
|
||||
The fields provided in these sequences should not contain sensitive
|
||||
information. Context IDs should not be considered confidential, but it is
|
||||
strongly recommended to generate them in a fashion that guarantees their
|
||||
sufficient uniqueness and avoids accidental or intended clashes with other
|
||||
contents.
|
||||
|
||||
## Examples
|
||||
|
||||
1. A new container `foobar` has been invoked by user `lennart` on host `zeta`:
|
||||
`OSC "3008;start=bed86fab93af4328bbed0a1224af6d40;type=container;user=lennart;hostname=zeta;machineid=3deb5353d3ba43d08201c136a47ead7b;bootid=d4a3d0fdf2e24fdea6d971ce73f4fbf2;pid=1062862;pidfdid=1063162;comm=systemd-nspawn;container=foobar" ST`
|
||||
|
||||
2. This context ends: `OSC "3008;end=bed86fab93af4328bbed0a1224af6d40" ST`
|
||||
|
||||
## Syntax in ABNF
|
||||
|
||||
```abnf
|
||||
OSC = %x1B %x5D
|
||||
ST = %x1B %x5C
|
||||
|
||||
DECIMAL = "0"-"9"
|
||||
HEX = "0"-"9" / "A"-"F" / "a-f"
|
||||
ID128 = 32*36(HEX / "-")
|
||||
UINT64 = 1*20DECIMAL
|
||||
ESCSEMICOLON = "\x3b"
|
||||
ESCBACKSLASH = "\x5c"
|
||||
SAFE = %x20-3a / %x3c-5b / %x5d-7e / ESCSEMICOLON / ESCBACKSLASH
|
||||
|
||||
CTXID = 1*64SAFE
|
||||
TYPEENUM = "service" / "session" / "shell" / "command" / "vm" / "container" / "elevate" / "chpriv" / "subcontext" / "remote" / "boot" / "app"
|
||||
|
||||
TYPE = "type=" TYPEENUM
|
||||
USER = "user=" 1*255SAFE
|
||||
HOSTNAME = "hostname=" 1*255SAFE
|
||||
MACHINEID = "machineid=" 1D128
|
||||
BOOTID = "bootid=" ID128
|
||||
PID = "pid=" UINT64
|
||||
PIDFDID = "pidfdid=" UINT64
|
||||
COMM = "comm=" 1*255SAFE
|
||||
CWD = "cwd=" 1*255SAFE
|
||||
CMDLINE = "cmdline=" *255SAFE
|
||||
VM = "vm=" 1*255SAFE
|
||||
CONTAINER = "container=" 1*255SAFE
|
||||
TARGETUSER = "targetuser=" 1*255SAFE
|
||||
TARGETHOST = "targethost=" 1*255SAFE
|
||||
SESSIONID = "sessionid=" 1*255SAFE
|
||||
|
||||
STARTFIELD = TYPE / USER / HOSTNAME / MACHINEID / BOOTID / PID / PIDFDID / COMM / CWD / CMDLINE / VM / CONTAINER / TARGETUSER / TARGETHOST / SESSIONID
|
||||
STARTSEQ = OSC "3008;start=" CTXID *(";" STARTFIELD) ST
|
||||
|
||||
EXITENUM = "success" / "failure" / "crash" / "interrupt"
|
||||
SIGNALENUM = "SIGBUS" / "SIGTRAP" / "SIGABRT" / "SIGSEGV" / …
|
||||
|
||||
EXIT = "exit=" EXITENUM
|
||||
STATUS = "status=" UINT64
|
||||
SIGNAL = "signal=" SIGNALENUM
|
||||
|
||||
ENDFIELD = EXIT / STATUS / SIGNAL
|
||||
ENDSEQ = OSC "3008;end=" CTXID *(";" ENDFIELD) ST
|
||||
```
|
||||
|
||||
## Known OSC Prefixes
|
||||
|
||||
Here's a list of OSC prefixes used by the various sequences currently in public
|
||||
use in various terminal emulators. It's not going to be complete, but I tried
|
||||
to do some reasonably thorough research to avoid conflicts with the new OSC
|
||||
sequence defined above.
|
||||
|
||||
| OSC Prefix | Purpose |
|
||||
|----------------:|------------------------------------------------------------|
|
||||
| `OSC "0;…"` | Icon name + window title |
|
||||
| `OSC "1;…"` | Icon name |
|
||||
| `OSC "2;…"` | Window title |
|
||||
| `OSC "3;…"` | X11 property |
|
||||
| `OSC "4;…"` | Palette |
|
||||
| `OSC "5;…"` | Special palette |
|
||||
| `OSC "6;…"` | Disable special color |
|
||||
| `OSC "7;…"` | Report cwd |
|
||||
| `OSC "8;…"` | Hyperlink |
|
||||
| `OSC "9;…"` | Progress bar (conemu) [conflict: also growl notifications] |
|
||||
| `OSC "10;…"` | Change colors |
|
||||
| `OSC "11;…"` | " |
|
||||
| `OSC "12;…"` | " |
|
||||
| `OSC "13;…"` | " |
|
||||
| `OSC "14;…"` | " |
|
||||
| `OSC "15;…"` | " |
|
||||
| `OSC "16;…"` | " |
|
||||
| `OSC "17;…"` | " |
|
||||
| `OSC "18;…"` | " |
|
||||
| `OSC "19;…"` | " |
|
||||
| `OSC "21;…"` | Query colors (kitty) |
|
||||
| `OSC "22;…"` | Cursor shape |
|
||||
| `OSC "46;…"` | Log file |
|
||||
| `OSC "50;…"` | Set font |
|
||||
| `OSC "51;…"` | Emacs shell |
|
||||
| `OSC "52;…"` | Manipulate selection data (aka clipboard) |
|
||||
| `OSC "60;…"` | Query allowed |
|
||||
| `OSC "61;…"` | Query disallowed |
|
||||
| `OSC "99;…"` | Notifications (kitty) |
|
||||
| `OSC "104;…"` | Reset color |
|
||||
| `OSC "105;…"` | Enable/disable special color |
|
||||
| `OSC "110;…"` | Reset colors |
|
||||
| `OSC "111;…"` | " |
|
||||
| `OSC "112;…"` | " |
|
||||
| `OSC "113;…"` | " |
|
||||
| `OSC "114;…"` | " |
|
||||
| `OSC "115;…"` | " |
|
||||
| `OSC "116;…"` | " |
|
||||
| `OSC "117;…"` | " |
|
||||
| `OSC "118;…"` | " |
|
||||
| `OSC "119;…"` | " |
|
||||
| `OSC "133;…"` | Prompt/command begin/command end (finalterm/iterm2) |
|
||||
| `OSC "440;…"` | Audio (mintty) |
|
||||
| `OSC "633;…"` | vscode action (Windows Terminal) |
|
||||
| `OSC "666;…"` | "termprop" (vte) |
|
||||
| `OSC "701;…"` | Locale (mintty) |
|
||||
| `OSC "777;…"` | Notification (rxvt) |
|
||||
| `OSC "3008;…"` | This specification |
|
||||
| `OSC "7704;…"` | ANSI colors (mintty) |
|
||||
| `OSC "7750;…"` | Emoji style (mintty) |
|
||||
| `OSC "7770;…"` | Font size (mintty) |
|
||||
| `OSC "7771;…"` | Glyph coverage (mintty) |
|
||||
| `OSC "7721:…"` | Copy window title (mintty) |
|
||||
| `OSC "7777;…"` | Window size (mintty) |
|
||||
| `OSC "9001;…"` | Action (Windows Terminal) |
|
||||
| `OSC "1337;…"` | iterm2 multiplex seeuqnece |
|
||||
| `OSC "5522;…"` | Clipboard (kitty) |
|
||||
| `OSC "30001;…"` | Push color onto stack (kitty) |
|
||||
| `OSC "30101;…"` | Pop color from stack (kitty) |
|
||||
| `OSC "77119;…"` | Wide chars (mintty) |
|
||||
@@ -233,6 +233,7 @@ if shellprofiledir == ''
|
||||
shellprofiledir = sysconfdir / 'profile.d'
|
||||
endif
|
||||
conf.set10('LINK_SHELL_EXTRA_DROPIN', shellprofiledir != 'no' and not shellprofiledir.startswith('/usr/'))
|
||||
conf.set10('LINK_OSC_CONTEXT_DROPIN', shellprofiledir != 'no' and not shellprofiledir.startswith('/usr/'))
|
||||
conf.set('SHELLPROFILEDIR', shellprofiledir, description : 'shell profile directory')
|
||||
|
||||
memory_accounting_default = get_option('memory-accounting-default')
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# shellcheck shell=bash
|
||||
# shellcheck disable=SC2016
|
||||
# shellcheck disable=SC1003
|
||||
|
||||
# This file is part of systemd.
|
||||
#
|
||||
# systemd is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
__systemd_osc_context_escape() {
|
||||
# Escape according to the OSC 8003 spec. Since this requires shelling out
|
||||
# to 'sed' we'll only do it where it's strictly necessary, and skip it when
|
||||
# processing strings we are pretty sure we won't need it for, such as
|
||||
# uuids, id128, hostnames, usernames, since they all come with syntax
|
||||
# requirements that exclude \ and ; anyway. This hence primarily is about
|
||||
# escaping the current working directory.
|
||||
echo "$1" | sed -e 's/\\/\\x5x/g' -e 's/;/\\x3b/g'
|
||||
}
|
||||
|
||||
__systemd_osc_context_common() {
|
||||
printf ";user=%s;hostname=%s;machineid=%s;bootid=%s;pid=%s" "$USER" "$HOSTNAME" "$(</etc/machine-id)" "$(</proc/sys/kernel/random/boot_id)" "$$"
|
||||
}
|
||||
|
||||
__systemd_osc_context_precmdline() {
|
||||
local systemd_exitstatus="$?"
|
||||
|
||||
# Close previous command
|
||||
if [ -n "${systemd_osc_context_cmd_id:-}" ]; then
|
||||
if [ "$systemd_exitstatus" -ge 127 ]; then
|
||||
printf "\033]8003;end=%s;exit=interrupt;signal=%s\033\\" "$systemd_osc_context_cmd_id" $((systemd_exitstatus-127))
|
||||
elif [ "$systemd_exitstatus" -ne 0 ]; then
|
||||
printf "\033]8003;end=%s;exit=failure;status=%s\033\\" "$systemd_osc_context_cmd_id" $((systemd_exitstatus))
|
||||
else
|
||||
printf "\033]8003;end=%s;exit=success\033\\" "$systemd_osc_context_cmd_id"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Prepare a context ID for this shell if we have none
|
||||
if [ -z "${systemd_osc_context_shell_id:-}" ]; then
|
||||
read -r systemd_osc_context_shell_id </proc/sys/kernel/random/uuid
|
||||
fi
|
||||
|
||||
# Create or update the shell session
|
||||
printf "\033]8003;start=%s%s;type=shell;cwd=%s\033\\" "$systemd_osc_context_shell_id" "$(__systemd_osc_context_common)" "$(__systemd_osc_context_escape "$PWD")"
|
||||
|
||||
# Prepare cmd id for next command
|
||||
read -r systemd_osc_context_cmd_id </proc/sys/kernel/random/uuid
|
||||
}
|
||||
|
||||
if [[ -n "${BASH_VERSION:-}" ]]; then
|
||||
# Whenever a new prompt is shown close the previous command, and prepare new command
|
||||
PROMPT_COMMAND+=(__systemd_osc_context_precmdline)
|
||||
|
||||
# PS0 is shown right after a prompt completed, but before the command is executed
|
||||
PS0='\033]8003;start=$systemd_osc_context_cmd_id$(__systemd_osc_context_common);type=command;cwd=$(__systemd_osc_context_escape "$PWD")\033\\'"${PS0:-}"
|
||||
fi
|
||||
@@ -1,6 +1,7 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
install_data('70-systemd-shell-extra.sh', install_dir : shellprofiledir.startswith('/usr/') ? shellprofiledir : libexecdir / 'profile.d')
|
||||
install_data('80-systemd-osc-context.sh', install_dir : shellprofiledir.startswith('/usr/') ? shellprofiledir : libexecdir / 'profile.d')
|
||||
|
||||
if conf.get('LINK_SHELL_EXTRA_DROPIN') == 1
|
||||
install_emptydir(shellprofiledir)
|
||||
@@ -8,3 +9,10 @@ if conf.get('LINK_SHELL_EXTRA_DROPIN') == 1
|
||||
meson.add_install_script(sh, '-c',
|
||||
ln_s.format(libexecdir / 'profile.d' / '70-systemd-shell-extra.sh', shellprofiledir / '70-systemd-shell-extra.sh'))
|
||||
endif
|
||||
|
||||
if conf.get('LINK_OSC_CONTEXT_DROPIN') == 1
|
||||
install_emptydir(shellprofiledir)
|
||||
|
||||
meson.add_install_script(sh, '-c',
|
||||
ln_s.format(libexecdir / 'profile.d' / '80-systemd-osc-context.sh', shellprofiledir / '80-systemd-osc-context.sh'))
|
||||
endif
|
||||
|
||||
@@ -1930,38 +1930,35 @@ int terminal_set_cursor_position(int fd, unsigned row, unsigned column) {
|
||||
return loop_write(fd, cursor_position, SIZE_MAX);
|
||||
}
|
||||
|
||||
int terminal_reset_defensive(int fd, bool switch_to_text) {
|
||||
int terminal_reset_defensive(int fd, TerminalResetFlags flags) {
|
||||
int r = 0;
|
||||
|
||||
assert(fd >= 0);
|
||||
assert(!FLAGS_SET(flags, TERMINAL_RESET_AVOID_ANSI_SEQ|TERMINAL_RESET_FORCE_ANSI_SEQ));
|
||||
|
||||
/* Resets the terminal comprehensively, but defensively. i.e. both resets the tty via ioctl()s and
|
||||
* via ANSI sequences, but avoids the latter in case we are talking to a pty. That's a safety measure
|
||||
* because ptys might be connected to shell pipelines where we cannot expect such ansi sequences to
|
||||
* work. Given that ptys are generally short-lived (and not recycled) this restriction shouldn't hurt
|
||||
* much.
|
||||
*
|
||||
* The specified fd should be open for *writing*! */
|
||||
/* Resets the terminal comprehensively, i.e. via both ioctl()s and via ANSI sequences, but do so only
|
||||
* if $TERM is unset or set to "dumb" */
|
||||
|
||||
if (!isatty_safe(fd))
|
||||
return -ENOTTY;
|
||||
|
||||
RET_GATHER(r, terminal_reset_ioctl(fd, switch_to_text));
|
||||
RET_GATHER(r, terminal_reset_ioctl(fd, FLAGS_SET(flags, TERMINAL_RESET_SWITCH_TO_TEXT)));
|
||||
|
||||
if (terminal_is_pty_fd(fd) == 0)
|
||||
if (!FLAGS_SET(flags, TERMINAL_RESET_AVOID_ANSI_SEQ) &&
|
||||
(FLAGS_SET(flags, TERMINAL_RESET_FORCE_ANSI_SEQ) || !getenv_terminal_is_dumb()))
|
||||
RET_GATHER(r, terminal_reset_ansi_seq(fd));
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
int terminal_reset_defensive_locked(int fd, bool switch_to_text) {
|
||||
int terminal_reset_defensive_locked(int fd, TerminalResetFlags flags) {
|
||||
assert(fd >= 0);
|
||||
|
||||
_cleanup_close_ int lock_fd = lock_dev_console();
|
||||
if (lock_fd < 0)
|
||||
log_debug_errno(lock_fd, "Failed to acquire lock for /dev/console, ignoring: %m");
|
||||
|
||||
return terminal_reset_defensive(fd, switch_to_text);
|
||||
return terminal_reset_defensive(fd, flags);
|
||||
}
|
||||
|
||||
void termios_disable_echo(struct termios *termios) {
|
||||
|
||||
@@ -41,8 +41,14 @@
|
||||
|
||||
bool isatty_safe(int fd);
|
||||
|
||||
int terminal_reset_defensive(int fd, bool switch_to_text);
|
||||
int terminal_reset_defensive_locked(int fd, bool switch_to_text);
|
||||
typedef enum TerminalResetFlags {
|
||||
TERMINAL_RESET_SWITCH_TO_TEXT = 1 << 0,
|
||||
TERMINAL_RESET_AVOID_ANSI_SEQ = 1 << 1,
|
||||
TERMINAL_RESET_FORCE_ANSI_SEQ = 1 << 2,
|
||||
} TerminalResetFlags;
|
||||
|
||||
int terminal_reset_defensive(int fd, TerminalResetFlags flags);
|
||||
int terminal_reset_defensive_locked(int fd, TerminalResetFlags flags);
|
||||
|
||||
int terminal_set_cursor_position(int fd, unsigned row, unsigned column);
|
||||
|
||||
|
||||
+17
-4
@@ -46,6 +46,7 @@
|
||||
#include "missing_securebits.h"
|
||||
#include "missing_syscall.h"
|
||||
#include "mkdir-label.h"
|
||||
#include "osc-context.h"
|
||||
#include "proc-cmdline.h"
|
||||
#include "process-util.h"
|
||||
#include "psi-util.h"
|
||||
@@ -668,7 +669,7 @@ static int setup_confirm_stdio(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = terminal_reset_defensive(fd, /* switch_to_text= */ true);
|
||||
r = terminal_reset_defensive(fd, TERMINAL_RESET_SWITCH_TO_TEXT);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -4367,6 +4368,10 @@ static void prepare_terminal(
|
||||
p->stdout_fd >= 0))
|
||||
return;
|
||||
|
||||
/* Let's explicitly determine whether to reset via ANSI sequences or not, taking our ExecContext
|
||||
* information into account */
|
||||
bool use_ansi = exec_context_shall_ansi_seq_reset(context);
|
||||
|
||||
if (context->tty_reset) {
|
||||
/* When we are resetting the TTY, then let's create a lock first, to synchronize access. This
|
||||
* in particular matters as concurrent resets and the TTY size ANSI DSR logic done by the
|
||||
@@ -4375,10 +4380,16 @@ static void prepare_terminal(
|
||||
if (lock_fd < 0)
|
||||
log_exec_debug_errno(context, p, lock_fd, "Failed to lock /dev/console, ignoring: %m");
|
||||
|
||||
(void) terminal_reset_defensive(STDOUT_FILENO, /* switch_to_text= */ false);
|
||||
/* We explicitly control whether to send ansi sequences or not here, since we want to consult
|
||||
* the env vars explicitly configured in the ExecContext, rather than our own environment
|
||||
* block. */
|
||||
(void) terminal_reset_defensive(STDOUT_FILENO, use_ansi ? TERMINAL_RESET_FORCE_ANSI_SEQ : TERMINAL_RESET_AVOID_ANSI_SEQ);
|
||||
}
|
||||
|
||||
(void) exec_context_apply_tty_size(context, STDIN_FILENO, STDOUT_FILENO, /* tty_path= */ NULL);
|
||||
|
||||
if (use_ansi)
|
||||
(void) osc_context_open_service(p->unit_id, p->invocation_id, /* ret_seq= */ NULL);
|
||||
}
|
||||
|
||||
int exec_invoke(
|
||||
@@ -4555,8 +4566,10 @@ int exec_invoke(
|
||||
* disallocate the VT), to get rid of any prior uses of the device. Note that we do not keep any fd
|
||||
* open here, hence some of the settings made here might vanish again, depending on the TTY driver
|
||||
* used. A 2nd ("constructive") initialization after we opened the input/output fds we actually want
|
||||
* will fix this. */
|
||||
exec_context_tty_reset(context, params);
|
||||
* will fix this. Note that we pass a NULL invocation ID here – as exec_context_tty_reset() expects
|
||||
* the invocation ID associated with the OSC 3008 context ID to close. But we don't want to close any
|
||||
* OSC 3008 context here, and opening a fresh OSC 3008 context happens a bit further down. */
|
||||
exec_context_tty_reset(context, params, /* invocation_id= */ SD_ID128_NULL);
|
||||
|
||||
if (params->shall_confirm_spawn && exec_context_shall_confirm_spawn(context)) {
|
||||
_cleanup_free_ char *cmdline = NULL;
|
||||
|
||||
+47
-8
@@ -39,6 +39,7 @@
|
||||
#include "format-util.h"
|
||||
#include "glob-util.h"
|
||||
#include "hexdecoct.h"
|
||||
#include "io-util.h"
|
||||
#include "ioprio-util.h"
|
||||
#include "lock-util.h"
|
||||
#include "log.h"
|
||||
@@ -50,6 +51,7 @@
|
||||
#include "missing_prctl.h"
|
||||
#include "mkdir-label.h"
|
||||
#include "namespace.h"
|
||||
#include "osc-context.h"
|
||||
#include "parse-util.h"
|
||||
#include "path-util.h"
|
||||
#include "process-util.h"
|
||||
@@ -129,11 +131,12 @@ int exec_context_apply_tty_size(
|
||||
rows == UINT_MAX ? &rows : NULL,
|
||||
cols == UINT_MAX ? &cols : NULL);
|
||||
|
||||
/* If we got nothing so far and we are talking to a physical device, and the TTY reset logic is on,
|
||||
* then let's query dimensions from the ANSI driver. */
|
||||
/* If we got nothing so far and we are talking to a physical device, then let's query dimensions from
|
||||
* the ANSI terminal driver. Note that we will not bother with this in case terminal reset via ansi
|
||||
* sequences is not enabled, as the DSR logic relies on ANSI sequences after all, and if we shall not
|
||||
* use those during initialization we need to skip it. */
|
||||
if (rows == UINT_MAX && cols == UINT_MAX &&
|
||||
context->tty_reset &&
|
||||
terminal_is_pty_fd(output_fd) == 0 &&
|
||||
exec_context_shall_ansi_seq_reset(context) &&
|
||||
isatty_safe(input_fd)) {
|
||||
r = terminal_get_size_by_dsr(input_fd, output_fd, &rows, &cols);
|
||||
if (r < 0)
|
||||
@@ -143,7 +146,7 @@ int exec_context_apply_tty_size(
|
||||
return terminal_set_size_fd(output_fd, tty_path, rows, cols);
|
||||
}
|
||||
|
||||
void exec_context_tty_reset(const ExecContext *context, const ExecParameters *p) {
|
||||
void exec_context_tty_reset(const ExecContext *context, const ExecParameters *p, sd_id128_t invocation_id) {
|
||||
_cleanup_close_ int _fd = -EBADF, lock_fd = -EBADF;
|
||||
int fd, r;
|
||||
|
||||
@@ -178,12 +181,32 @@ void exec_context_tty_reset(const ExecContext *context, const ExecParameters *p)
|
||||
log_warning_errno(lock_fd, "Failed to lock /dev/console, proceeding without lock: %m");
|
||||
|
||||
if (context->tty_reset)
|
||||
(void) terminal_reset_defensive(fd, /* switch_to_text= */ true);
|
||||
(void) terminal_reset_defensive(
|
||||
fd,
|
||||
TERMINAL_RESET_SWITCH_TO_TEXT |
|
||||
(exec_context_shall_ansi_seq_reset(context) ? TERMINAL_RESET_FORCE_ANSI_SEQ : TERMINAL_RESET_AVOID_ANSI_SEQ));
|
||||
|
||||
r = exec_context_apply_tty_size(context, fd, fd, path);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to configure TTY dimensions, ignoring: %m");
|
||||
|
||||
if (!sd_id128_is_null(invocation_id)) {
|
||||
sd_id128_t context_id;
|
||||
|
||||
r = osc_context_id_from_invocation_id(invocation_id, &context_id);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to derive context ID from invocation ID, ignoring: %m");
|
||||
else {
|
||||
_cleanup_free_ char *seq = NULL;
|
||||
|
||||
r = osc_context_close(context_id, &seq);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to acquire OSC close sequence, ignoring: %m");
|
||||
else
|
||||
(void) loop_write(fd, seq, SIZE_MAX);
|
||||
}
|
||||
}
|
||||
|
||||
if (context->tty_vhangup)
|
||||
(void) terminal_vhangup_fd(fd);
|
||||
|
||||
@@ -974,6 +997,22 @@ bool exec_context_may_touch_console(const ExecContext *ec) {
|
||||
tty_may_match_dev_console(exec_context_tty_path(ec));
|
||||
}
|
||||
|
||||
bool exec_context_shall_ansi_seq_reset(const ExecContext *c) {
|
||||
assert(c);
|
||||
|
||||
/* Determines whether ANSI sequences shall be used during any terminal initialisation:
|
||||
*
|
||||
* 1. If the reset logic is enabled at all, this is an immediate no.
|
||||
*
|
||||
* 2. If $TERM is set to anything other than "dumb", it's a yes.
|
||||
*/
|
||||
|
||||
if (!c->tty_reset)
|
||||
return false;
|
||||
|
||||
return !streq_ptr(strv_env_get(c->environment, "TERM"), "dumb");
|
||||
}
|
||||
|
||||
static void strv_fprintf(FILE *f, char **l) {
|
||||
assert(f);
|
||||
|
||||
@@ -1610,7 +1649,7 @@ void exec_context_free_log_extra_fields(ExecContext *c) {
|
||||
c->n_log_extra_fields = 0;
|
||||
}
|
||||
|
||||
void exec_context_revert_tty(ExecContext *c) {
|
||||
void exec_context_revert_tty(ExecContext *c, sd_id128_t invocation_id) {
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
const char *path;
|
||||
struct stat st;
|
||||
@@ -1619,7 +1658,7 @@ void exec_context_revert_tty(ExecContext *c) {
|
||||
assert(c);
|
||||
|
||||
/* First, reset the TTY (possibly kicking everybody else from the TTY) */
|
||||
exec_context_tty_reset(c, /* parameters= */ NULL);
|
||||
exec_context_tty_reset(c, /* parameters= */ NULL, invocation_id);
|
||||
|
||||
/* And then undo what chown_terminal() did earlier. Note that we only do this if we have a path
|
||||
* configured. If the TTY was passed to us as file descriptor we assume the TTY is opened and managed
|
||||
|
||||
+3
-2
@@ -530,6 +530,7 @@ const char* exec_context_fdname(const ExecContext *c, int fd_index);
|
||||
|
||||
bool exec_context_may_touch_console(const ExecContext *c);
|
||||
bool exec_context_maintains_privileges(const ExecContext *c);
|
||||
bool exec_context_shall_ansi_seq_reset(const ExecContext *c);
|
||||
|
||||
int exec_context_get_effective_ioprio(const ExecContext *c);
|
||||
bool exec_context_get_effective_mount_apivfs(const ExecContext *c);
|
||||
@@ -537,14 +538,14 @@ bool exec_context_get_effective_bind_log_sockets(const ExecContext *c);
|
||||
|
||||
void exec_context_free_log_extra_fields(ExecContext *c);
|
||||
|
||||
void exec_context_revert_tty(ExecContext *c);
|
||||
void exec_context_revert_tty(ExecContext *c, sd_id128_t invocation_id);
|
||||
|
||||
int exec_context_get_clean_directories(ExecContext *c, char **prefix, ExecCleanMask mask, char ***ret);
|
||||
int exec_context_get_clean_mask(ExecContext *c, ExecCleanMask *ret);
|
||||
|
||||
const char* exec_context_tty_path(const ExecContext *context);
|
||||
int exec_context_apply_tty_size(const ExecContext *context, int input_fd, int output_fd, const char *tty_path);
|
||||
void exec_context_tty_reset(const ExecContext *context, const ExecParameters *p);
|
||||
void exec_context_tty_reset(const ExecContext *context, const ExecParameters *p, sd_id128_t invocation_id);
|
||||
|
||||
uint64_t exec_context_get_rlimit(const ExecContext *c, const char *name);
|
||||
int exec_context_get_oom_score_adjust(const ExecContext *c);
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
#include "efivars.h"
|
||||
#include "emergency-action.h"
|
||||
#include "env-util.h"
|
||||
#include "escape.h"
|
||||
#include "exit-status.h"
|
||||
#include "fd-util.h"
|
||||
#include "fdset.h"
|
||||
@@ -57,6 +58,7 @@
|
||||
#include "ima-setup.h"
|
||||
#include "import-creds.h"
|
||||
#include "initrd-util.h"
|
||||
#include "io-util.h"
|
||||
#include "ipe-setup.h"
|
||||
#include "killall.h"
|
||||
#include "kmod-setup.h"
|
||||
@@ -73,6 +75,7 @@
|
||||
#include "mount-setup.h"
|
||||
#include "mount-util.h"
|
||||
#include "os-util.h"
|
||||
#include "osc-context.h"
|
||||
#include "pager.h"
|
||||
#include "parse-argument.h"
|
||||
#include "parse-util.h"
|
||||
@@ -2382,6 +2385,38 @@ static void log_execution_mode(bool *ret_first_boot) {
|
||||
*ret_first_boot = first_boot;
|
||||
}
|
||||
|
||||
static int write_boot_or_shutdown_osc(const char *type) {
|
||||
int r;
|
||||
|
||||
assert(STRPTR_IN_SET(type, "boot", "shutdown"));
|
||||
|
||||
if (getenv_terminal_is_dumb())
|
||||
return 0;
|
||||
|
||||
_cleanup_close_ int fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC);
|
||||
if (fd < 0)
|
||||
return log_debug_errno(fd, "Failed to open /dev/console to print %s OSC, ignoring: %m", type);
|
||||
|
||||
_cleanup_free_ char *seq = NULL;
|
||||
if (streq(type, "boot"))
|
||||
r = osc_context_open_boot(&seq);
|
||||
else
|
||||
r = osc_context_close(SD_ID128_ALLF, &seq);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to acquire %s OSC sequence, ignoring: %m", type);
|
||||
|
||||
r = loop_write(fd, seq, SIZE_MAX);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to write %s OSC sequence, ignoring: %m", type);
|
||||
|
||||
if (DEBUG_LOGGING) {
|
||||
_cleanup_free_ char *h = cescape(seq);
|
||||
log_debug("OSC sequence for %s successfully written: %s", type, strna(h));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int initialize_runtime(
|
||||
bool skip_setup,
|
||||
bool first_boot,
|
||||
@@ -2440,6 +2475,8 @@ static int initialize_runtime(
|
||||
|
||||
write_container_id();
|
||||
|
||||
(void) write_boot_or_shutdown_osc("boot");
|
||||
|
||||
/* Copy os-release to the propagate directory, so that we update it for services running
|
||||
* under RootDirectory=/RootImage= when we do a soft reboot. */
|
||||
r = setup_os_release(RUNTIME_SCOPE_SYSTEM);
|
||||
@@ -3422,6 +3459,8 @@ finish:
|
||||
}
|
||||
#endif
|
||||
|
||||
(void) write_boot_or_shutdown_osc("shutdown");
|
||||
|
||||
if (r < 0)
|
||||
(void) sd_notifyf(/* unset_environment= */ false,
|
||||
"ERRNO=%i", -r);
|
||||
|
||||
+1
-1
@@ -2178,7 +2178,7 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart)
|
||||
(void) unlink(s->pid_file);
|
||||
|
||||
/* Reset TTY ownership if necessary */
|
||||
exec_context_revert_tty(&s->exec_context);
|
||||
exec_context_revert_tty(&s->exec_context, UNIT(s)->invocation_id);
|
||||
}
|
||||
|
||||
static void service_enter_stop_post(Service *s, ServiceResult f) {
|
||||
|
||||
@@ -121,7 +121,7 @@ static void print_welcome(int rfd) {
|
||||
pn = os_release_pretty_name(pretty_name, os_name);
|
||||
ac = isempty(ansi_color) ? "0" : ansi_color;
|
||||
|
||||
(void) terminal_reset_defensive_locked(STDOUT_FILENO, /* switch_to_text= */ false);
|
||||
(void) terminal_reset_defensive_locked(STDOUT_FILENO, /* flags= */ 0);
|
||||
|
||||
if (colors_enabled())
|
||||
printf("\nWelcome to your new installation of \x1B[%sm%s\x1B[0m!\n", ac, pn);
|
||||
|
||||
+1
-1
@@ -2515,7 +2515,7 @@ static int create_interactively(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
(void) terminal_reset_defensive_locked(STDOUT_FILENO, /* switch_to_text= */ false);
|
||||
(void) terminal_reset_defensive_locked(STDOUT_FILENO, /* flags= */ 0);
|
||||
|
||||
for (;;) {
|
||||
username = mfree(username);
|
||||
|
||||
+142
-7
@@ -39,11 +39,13 @@
|
||||
#include "format-util.h"
|
||||
#include "fs-util.h"
|
||||
#include "hostname-util.h"
|
||||
#include "io-util.h"
|
||||
#include "json-util.h"
|
||||
#include "locale-util.h"
|
||||
#include "login-util.h"
|
||||
#include "macro.h"
|
||||
#include "missing_syscall.h"
|
||||
#include "osc-context.h"
|
||||
#include "pam-util.h"
|
||||
#include "parse-util.h"
|
||||
#include "path-util.h"
|
||||
@@ -1081,6 +1083,7 @@ static int register_session(
|
||||
UserRecord *ur,
|
||||
bool debug,
|
||||
char **ret_seat,
|
||||
char **ret_type,
|
||||
char **ret_runtime_dir) {
|
||||
|
||||
int r;
|
||||
@@ -1089,19 +1092,20 @@ static int register_session(
|
||||
assert(c);
|
||||
assert(ur);
|
||||
assert(ret_seat);
|
||||
assert(ret_type);
|
||||
assert(ret_runtime_dir);
|
||||
|
||||
/* We don't register session class none with logind */
|
||||
if (streq(c->class, "none")) {
|
||||
pam_debug_syslog(handle, debug, "Skipping logind registration for session class none.");
|
||||
*ret_seat = *ret_runtime_dir = NULL;
|
||||
*ret_seat = *ret_type = *ret_runtime_dir = NULL;
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
|
||||
/* Make most of this a NOP on non-logind systems */
|
||||
if (!logind_running()) {
|
||||
pam_debug_syslog(handle, debug, "Skipping logind registration as logind is not running.");
|
||||
*ret_seat = *ret_runtime_dir = NULL;
|
||||
*ret_seat = *ret_type = *ret_runtime_dir = NULL;
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -1174,7 +1178,7 @@ static int register_session(
|
||||
if (streq_ptr(error_id, "io.systemd.Login.AlreadySessionMember")) {
|
||||
/* We are already in a session, don't do anything */
|
||||
pam_debug_syslog(handle, debug, "Not creating session: %s", error_id);
|
||||
*ret_seat = *ret_runtime_dir = NULL;
|
||||
*ret_seat = *ret_type= *ret_runtime_dir = NULL;
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
if (error_id)
|
||||
@@ -1271,7 +1275,7 @@ static int register_session(
|
||||
/* We are already in a session, don't do anything */
|
||||
pam_debug_syslog(handle, debug,
|
||||
"Not creating session: %s", bus_error_message(&error, r));
|
||||
*ret_seat = *ret_runtime_dir = NULL;
|
||||
*ret_seat = *ret_type = *ret_runtime_dir = NULL;
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -1311,6 +1315,10 @@ static int register_session(
|
||||
* somewhere else (for example PAM module parameters). Let's now update the environment variables, so that this
|
||||
* data is inherited into the session processes, and programs can rely on them to be initialized. */
|
||||
|
||||
_cleanup_free_ char *real_type = strdup(c->type); /* make copy because this might point to env block, which we are going to update shortly */
|
||||
if (!real_type)
|
||||
return pam_log_oom(handle);
|
||||
|
||||
r = update_environment(handle, "XDG_SESSION_TYPE", c->type);
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
@@ -1367,6 +1375,7 @@ static int register_session(
|
||||
|
||||
c->vtnr = real_vtnr;
|
||||
c->seat = *ret_seat = TAKE_PTR(rs);
|
||||
c->type = *ret_type = TAKE_PTR(real_type);
|
||||
*ret_runtime_dir = TAKE_PTR(rt);
|
||||
|
||||
return PAM_SUCCESS;
|
||||
@@ -1574,6 +1583,126 @@ static int setup_environment(
|
||||
return export_legacy_dbus_address(handle, runtime_directory);
|
||||
}
|
||||
|
||||
static int open_osc_context(pam_handle_t *handle, const char *session_type, UserRecord *ur) {
|
||||
int r;
|
||||
|
||||
assert(handle);
|
||||
assert(ur);
|
||||
|
||||
/* If this is a TTY session, then output the session start OSC sequence */
|
||||
|
||||
if (!streq_ptr(session_type, "tty"))
|
||||
return PAM_SUCCESS;
|
||||
|
||||
const char *e = pam_getenv(handle, "TERM");
|
||||
if (!e)
|
||||
e = getenv("TERM");
|
||||
if (streq_ptr(e, "dumb"))
|
||||
return PAM_SUCCESS;
|
||||
|
||||
/* NB: we output directly to stdout, instead of going via pam_info() or so, because that's too
|
||||
* high-level for us, as it suffixes the output with a newline, expecting a full blown text message
|
||||
* as prompt string, not just an ANSI sequence. Note that PAM's conv_misc() actually goes to stdout
|
||||
* anyway, hence let's do so here too, but only after careful validation. */
|
||||
if (!isatty(STDOUT_FILENO))
|
||||
return PAM_SUCCESS;
|
||||
|
||||
/* Keep a reference to the TTY we are operating on, so that we can issue the OSC close sequence also
|
||||
* if the TTY is already closed. We use an O_PATH reference here, rather than a properly opened fd,
|
||||
* so that we don't delay tty hang-up. */
|
||||
_cleanup_close_ int tty_opath_fd = fd_reopen(STDOUT_FILENO, O_PATH|O_CLOEXEC);
|
||||
if (tty_opath_fd < 0)
|
||||
pam_syslog_errno(handle, LOG_DEBUG, tty_opath_fd, "Failed to pin TTY, ignoring: %m");
|
||||
else
|
||||
tty_opath_fd = fd_move_above_stdio(tty_opath_fd);
|
||||
|
||||
_cleanup_free_ char *osc = NULL;
|
||||
sd_id128_t osc_id;
|
||||
r = osc_context_open_session(
|
||||
ur->user_name,
|
||||
pam_getenv(handle, "XDG_SESSION_ID"),
|
||||
&osc,
|
||||
&osc_id);
|
||||
if (r < 0)
|
||||
return pam_syslog_errno(handle, LOG_ERR, r, "Failed to prepare OSC sequence: %m");
|
||||
|
||||
r = loop_write(STDOUT_FILENO, osc, SIZE_MAX);
|
||||
if (r < 0)
|
||||
return pam_syslog_errno(handle, LOG_ERR, r, "Failed to write OSC sequence to TTY: %m");
|
||||
|
||||
/* Remember the OSC context id, so that we can close it cleanly later */
|
||||
_cleanup_free_ sd_id128_t *osc_id_copy = newdup(sd_id128_t, &osc_id, 1);
|
||||
if (!osc_id_copy)
|
||||
return pam_log_oom(handle);
|
||||
|
||||
r = pam_set_data(handle, "systemd.osc-context-id", osc_id_copy, pam_cleanup_free);
|
||||
if (r != PAM_SUCCESS)
|
||||
return pam_syslog_pam_error(handle, LOG_ERR, r,
|
||||
"Failed to set PAM OSC sequence ID data: @PAMERR@");
|
||||
|
||||
TAKE_PTR(osc_id_copy);
|
||||
|
||||
if (tty_opath_fd >= 0) {
|
||||
r = pam_set_data(handle, "systemd.osc-context-fd", FD_TO_PTR(tty_opath_fd), pam_cleanup_close);
|
||||
if (r != PAM_SUCCESS)
|
||||
return pam_syslog_pam_error(handle, LOG_ERR, r,
|
||||
"Failed to set PAM OSC sequence fd data: @PAMERR@");
|
||||
|
||||
TAKE_FD(tty_opath_fd);
|
||||
}
|
||||
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
|
||||
static int close_osc_context(pam_handle_t *handle) {
|
||||
int r;
|
||||
|
||||
assert(handle);
|
||||
|
||||
const void *p;
|
||||
int tty_opath_fd = -EBADF;
|
||||
r = pam_get_data(handle, "systemd.osc-context-fd", &p);
|
||||
if (r == PAM_SUCCESS)
|
||||
tty_opath_fd = PTR_TO_FD(p);
|
||||
else if (r != PAM_NO_MODULE_DATA)
|
||||
return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM OSC context fd: @PAMERR@");
|
||||
if (tty_opath_fd < 0)
|
||||
return PAM_SUCCESS;
|
||||
|
||||
const sd_id128_t *osc_id = NULL;
|
||||
r = pam_get_data(handle, "systemd.osc-context-id", (const void**) &osc_id);
|
||||
if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA))
|
||||
return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM OSC context id data: @PAMERR@");
|
||||
if (!osc_id)
|
||||
return PAM_SUCCESS;
|
||||
|
||||
/* Now open the original TTY again, so that we can write on it */
|
||||
_cleanup_close_ int fd = fd_reopen(tty_opath_fd, O_WRONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
|
||||
if (fd < 0) {
|
||||
pam_syslog_errno(handle, LOG_DEBUG, fd, "Failed to reopen TTY, ignoring: %m");
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
|
||||
/* /bin/login calls us with fds 0, 1, 2 closed, which is just weird. Let's step outside of that
|
||||
* range, just in case pam_syslog() or so logs to stderr */
|
||||
fd = fd_move_above_stdio(fd);
|
||||
|
||||
/* Safety check, let's verify this is a valid TTY we just opened */
|
||||
if (!isatty(fd))
|
||||
return PAM_SUCCESS;
|
||||
|
||||
_cleanup_free_ char *osc = NULL;
|
||||
r = osc_context_close(*osc_id, &osc);
|
||||
if (r < 0)
|
||||
return pam_syslog_errno(handle, LOG_ERR, r, "Failed to prepare OSC sequence: %m");
|
||||
|
||||
r = loop_write(fd, osc, SIZE_MAX);
|
||||
if (r < 0)
|
||||
return pam_syslog_errno(handle, LOG_ERR, r, "Failed to write OSC sequence to TTY: %m");
|
||||
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
|
||||
_public_ PAM_EXTERN int pam_sm_open_session(
|
||||
pam_handle_t *handle,
|
||||
int flags,
|
||||
@@ -1637,8 +1766,8 @@ _public_ PAM_EXTERN int pam_sm_open_session(
|
||||
|
||||
session_context_mangle(handle, &c, ur, debug);
|
||||
|
||||
_cleanup_free_ char *seat_buffer = NULL, *runtime_dir = NULL;
|
||||
r = register_session(handle, &c, ur, debug, &seat_buffer, &runtime_dir);
|
||||
_cleanup_free_ char *seat_buffer = NULL, *type_buffer = NULL, *runtime_dir = NULL;
|
||||
r = register_session(handle, &c, ur, debug, &seat_buffer, &type_buffer, &runtime_dir);
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
|
||||
@@ -1653,7 +1782,11 @@ _public_ PAM_EXTERN int pam_sm_open_session(
|
||||
if (default_capability_ambient_set == UINT64_MAX)
|
||||
default_capability_ambient_set = pick_default_capability_ambient_set(ur, c.service, c.seat);
|
||||
|
||||
return apply_user_record_settings(handle, ur, debug, default_capability_bounding_set, default_capability_ambient_set);
|
||||
r = apply_user_record_settings(handle, ur, debug, default_capability_bounding_set, default_capability_ambient_set);
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
|
||||
return open_osc_context(handle, c.type, ur);
|
||||
}
|
||||
|
||||
_public_ PAM_EXTERN int pam_sm_close_session(
|
||||
@@ -1690,6 +1823,8 @@ _public_ PAM_EXTERN int pam_sm_close_session(
|
||||
return pam_syslog_pam_error(handle, LOG_ERR, r,
|
||||
"Failed to get PAM systemd.existing data: @PAMERR@");
|
||||
|
||||
(void) close_osc_context(handle);
|
||||
|
||||
id = pam_getenv(handle, "XDG_SESSION_ID");
|
||||
if (id && !existing) {
|
||||
_cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
#include "main-func.h"
|
||||
#include "mkdir.h"
|
||||
#include "nulstr-util.h"
|
||||
#include "osc-context.h"
|
||||
#include "pager.h"
|
||||
#include "parse-argument.h"
|
||||
#include "parse-util.h"
|
||||
@@ -1222,6 +1223,13 @@ static int process_forward(sd_event *event, sd_bus_slot *machine_removed_slot, i
|
||||
log_info("Connected to machine %s. Press ^] three times within 1s to exit session.", name);
|
||||
}
|
||||
|
||||
_cleanup_(osc_context_closep) sd_id128_t osc_context_id = SD_ID128_NULL;
|
||||
if (!terminal_is_dumb()) {
|
||||
r = osc_context_open_container(name, /* ret_seq= */ NULL, &osc_context_id);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
r = sd_event_set_signal_exit(event, true);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to enable SIGINT/SITERM handling: %m");
|
||||
|
||||
+11
-3
@@ -85,6 +85,7 @@
|
||||
#include "nsresource.h"
|
||||
#include "nulstr-util.h"
|
||||
#include "os-util.h"
|
||||
#include "osc-context.h"
|
||||
#include "pager.h"
|
||||
#include "parse-argument.h"
|
||||
#include "parse-util.h"
|
||||
@@ -3581,9 +3582,9 @@ static int inner_child(
|
||||
/* LXC sets container=lxc, so follow the scheme here */
|
||||
envp[n_env++] = strjoina("container=", arg_container_service_name);
|
||||
|
||||
envp[n_env] = strv_find_prefix(environ, "TERM=");
|
||||
if (envp[n_env])
|
||||
n_env++;
|
||||
/* Propagate $TERM unless we are invoked in pipe mode and stdin/stdout/stderr don't refer to a TTY */
|
||||
const char *term = (arg_console_mode != CONSOLE_PIPE || on_tty()) ? strv_find_prefix(environ, "TERM=") : NULL;
|
||||
envp[n_env++] = (char*) (term ?: "TERM=dumb");
|
||||
|
||||
if (home || !uid_is_valid(arg_uid) || arg_uid == 0)
|
||||
if (asprintf(envp + n_env++, "HOME=%s", home ?: "/root") < 0)
|
||||
@@ -5645,6 +5646,13 @@ static int run_container(
|
||||
(void) expose_port_execute(rtnl, &expose_args->fw_ctx, arg_expose_ports, AF_INET6, &expose_args->address6);
|
||||
}
|
||||
|
||||
_cleanup_(osc_context_closep) sd_id128_t osc_context_id = SD_ID128_NULL;
|
||||
if (IN_SET(arg_console_mode, CONSOLE_INTERACTIVE, CONSOLE_READ_ONLY) && !terminal_is_dumb()) {
|
||||
r = osc_context_open_container(arg_machine, /* ret_seq= */ NULL, &osc_context_id);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (arg_console_mode != CONSOLE_PIPE) {
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
PTYForwardFlags flags = 0;
|
||||
|
||||
+21
-5
@@ -31,6 +31,7 @@
|
||||
#include "fs-util.h"
|
||||
#include "hostname-util.h"
|
||||
#include "main-func.h"
|
||||
#include "osc-context.h"
|
||||
#include "parse-argument.h"
|
||||
#include "parse-util.h"
|
||||
#include "path-util.h"
|
||||
@@ -1232,7 +1233,7 @@ static int transient_kill_set_properties(sd_bus_message *m) {
|
||||
}
|
||||
|
||||
static int transient_service_set_properties(sd_bus_message *m, const char *pty_path, int pty_fd) {
|
||||
bool send_term = false;
|
||||
int send_term = false; /* tri-state */
|
||||
int r;
|
||||
|
||||
/* We disable environment expansion on the server side via ExecStartEx=:.
|
||||
@@ -1311,14 +1312,22 @@ static int transient_service_set_properties(sd_bus_message *m, const char *pty_p
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
send_term = isatty_safe(STDIN_FILENO) || isatty_safe(STDOUT_FILENO) || isatty_safe(STDERR_FILENO);
|
||||
send_term = -1;
|
||||
}
|
||||
|
||||
if (send_term) {
|
||||
if (send_term != 0) {
|
||||
const char *e;
|
||||
|
||||
e = getenv("TERM");
|
||||
if (e) {
|
||||
/* Propagate $TERM only if we are actually connected to a TTY */
|
||||
if (isatty_safe(STDIN_FILENO) || isatty_safe(STDOUT_FILENO) || isatty_safe(STDERR_FILENO)) {
|
||||
e = getenv("TERM");
|
||||
send_term = true;
|
||||
} else
|
||||
/* If we are not connected to any TTY ourselves, then send TERM=dumb, but only if we
|
||||
* really need to (because we actually allocated a TTY for the service) */
|
||||
e = "dumb";
|
||||
|
||||
if (send_term > 0) {
|
||||
_cleanup_free_ char *n = NULL;
|
||||
|
||||
n = strjoin("TERM=", e);
|
||||
@@ -2194,7 +2203,14 @@ static int start_transient_service(sd_bus *bus) {
|
||||
if (!c.bus_path)
|
||||
return log_oom();
|
||||
|
||||
_cleanup_(osc_context_closep) sd_id128_t osc_context_id = SD_ID128_NULL;
|
||||
if (pty_fd >= 0) {
|
||||
if (!terminal_is_dumb() && arg_exec_user) {
|
||||
r = osc_context_open_chpriv(arg_exec_user, /* ret_seq= */ NULL, &osc_context_id);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
(void) sd_event_set_signal_exit(c.event, true);
|
||||
|
||||
if (!arg_quiet)
|
||||
|
||||
@@ -132,6 +132,7 @@ shared_sources = files(
|
||||
'numa-util.c',
|
||||
'open-file.c',
|
||||
'openssl-util.c',
|
||||
'osc-context.c',
|
||||
'output-mode.c',
|
||||
'pager.c',
|
||||
'parse-argument.c',
|
||||
|
||||
@@ -0,0 +1,342 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <sys/auxv.h>
|
||||
|
||||
#include "escape.h"
|
||||
#include "hostname-util.h"
|
||||
#include "id128-util.h"
|
||||
#include "osc-context.h"
|
||||
#include "pidfd-util.h"
|
||||
#include "process-util.h"
|
||||
#include "string-util.h"
|
||||
#include "terminal-util.h"
|
||||
#include "user-util.h"
|
||||
|
||||
/* This currently generates open sequences for OSC 3008 types "boot", "container", "vm", "elevate", "chpriv",
|
||||
* "subcontext", "session", "sevice".
|
||||
*
|
||||
* NB: Not generated by systemd: "remote" (would have to be generated from the SSH client), "app". */
|
||||
|
||||
static int strextend_escaped(char **s, const char *prefix, const char *value) {
|
||||
assert(s);
|
||||
assert(value);
|
||||
|
||||
if (!strextend(s, prefix))
|
||||
return -ENOMEM;
|
||||
|
||||
_cleanup_free_ char *e = xescape(value, ";\\");
|
||||
if (!e)
|
||||
return -ENOMEM;
|
||||
|
||||
if (!strextend(s, e))
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int osc_append_identity(char **s) {
|
||||
int r;
|
||||
|
||||
assert(s);
|
||||
|
||||
_cleanup_free_ char *u = getusername_malloc();
|
||||
if (u) {
|
||||
r = strextend_escaped(s, ";user=", u);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
_cleanup_free_ char *h = gethostname_malloc();
|
||||
if (h) {
|
||||
r = strextend_escaped(s, ";hostname=", h);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
sd_id128_t id;
|
||||
if (sd_id128_get_machine(&id) >= 0) {
|
||||
r = strextendf(s, ";machineid=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(id));
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (sd_id128_get_boot(&id) >= 0) {
|
||||
r = strextendf(s, ";bootid=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(id));
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
r = strextendf(s, ";pid=" PID_FMT, getpid_cached());
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
uint64_t pidfdid;
|
||||
if (pidfd_get_inode_id_self_cached(&pidfdid) >= 0) {
|
||||
r = strextendf(s, ";pidfdid=%" PRIu64, pidfdid);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
r = strextend_escaped(s, ";comm=", program_invocation_short_name);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void osc_context_default_id(sd_id128_t *ret_id) {
|
||||
|
||||
/* Usually we only want one context ID per tool. Since we don't want to store the ID let's just hash
|
||||
* one from process credentials */
|
||||
|
||||
struct {
|
||||
const char label[32];
|
||||
uint64_t pidfdid;
|
||||
uint8_t auxval[16];
|
||||
pid_t pid;
|
||||
} data = {
|
||||
.label = "systemd context hash bytes v1",
|
||||
.pid = getpid_cached(),
|
||||
};
|
||||
|
||||
assert(ret_id);
|
||||
|
||||
memcpy(data.auxval, ULONG_TO_PTR(getauxval(AT_RANDOM)), sizeof(data.auxval));
|
||||
(void) pidfd_get_inode_id_self_cached(&data.pidfdid);
|
||||
|
||||
*ret_id = id128_digest(&data, sizeof(data));
|
||||
}
|
||||
|
||||
static int osc_context_intro_raw(sd_id128_t id, char **ret_seq) {
|
||||
int r;
|
||||
|
||||
assert(ret_seq);
|
||||
|
||||
_cleanup_free_ char *seq = NULL;
|
||||
if (asprintf(&seq, ANSI_OSC "3008;start=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(id)) < 0)
|
||||
return -ENOMEM;
|
||||
|
||||
r = osc_append_identity(&seq);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
*ret_seq = TAKE_PTR(seq);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int osc_context_intro(char **ret_seq, sd_id128_t *ret_context_id) {
|
||||
int r;
|
||||
|
||||
assert(ret_seq);
|
||||
|
||||
/* If the user passed us a buffer for the context ID generate a randomized one, since we have a place
|
||||
* to store it. The user should pass the ID back to osc_context_close() later on. If the user did not
|
||||
* pass us a buffer, we'll use a session ID hashed from process properties that remain stable as long
|
||||
* our process exists. It hence also remains stable across reexec and similar. */
|
||||
sd_id128_t id;
|
||||
if (ret_context_id) {
|
||||
r = sd_id128_randomize(&id);
|
||||
if (r < 0)
|
||||
return r;
|
||||
} else
|
||||
osc_context_default_id(&id);
|
||||
|
||||
_cleanup_free_ char *seq = NULL;
|
||||
r = osc_context_intro_raw(id, &seq);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (ret_context_id)
|
||||
*ret_context_id = id;
|
||||
|
||||
*ret_seq = TAKE_PTR(seq);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int osc_context_outro(char *_seq, sd_id128_t id, char **ret_seq, sd_id128_t *ret_context_id) {
|
||||
_cleanup_free_ char *seq = TAKE_PTR(_seq); /* We take possession of the string no matter what */
|
||||
|
||||
if (ret_seq)
|
||||
*ret_seq = TAKE_PTR(seq);
|
||||
else {
|
||||
fputs(seq, stdout);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
if (ret_context_id)
|
||||
*ret_context_id = id;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int osc_context_open_boot(char **ret_seq) {
|
||||
int r;
|
||||
|
||||
_cleanup_free_ char *seq = NULL;
|
||||
r = osc_context_intro(&seq, /* ret_context_id= */ NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!strextend(&seq, ";type=boot" ANSI_ST))
|
||||
return -ENOMEM;
|
||||
|
||||
return osc_context_outro(TAKE_PTR(seq), /* context_id= */ SD_ID128_NULL, ret_seq, /* ret_context_id= */ NULL);
|
||||
}
|
||||
|
||||
int osc_context_open_container(const char *name, char **ret_seq, sd_id128_t *ret_context_id) {
|
||||
int r;
|
||||
|
||||
_cleanup_free_ char *seq = NULL;
|
||||
sd_id128_t id = SD_ID128_NULL;
|
||||
r = osc_context_intro(&seq, ret_context_id ? &id : NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (name) {
|
||||
r = strextend_escaped(&seq, ";container=", name);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (!strextend(&seq, ";type=container" ANSI_ST))
|
||||
return -ENOMEM;
|
||||
|
||||
return osc_context_outro(TAKE_PTR(seq), id, ret_seq, ret_context_id);
|
||||
}
|
||||
|
||||
int osc_context_open_vm(const char *name, char **ret_seq, sd_id128_t *ret_context_id) {
|
||||
int r;
|
||||
|
||||
assert(name);
|
||||
|
||||
_cleanup_free_ char *seq = NULL;
|
||||
sd_id128_t id = SD_ID128_NULL;
|
||||
r = osc_context_intro(&seq, ret_context_id ? &id : NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = strextend_escaped(&seq, ";vm=", name);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!strextend(&seq, ";type=vm" ANSI_ST))
|
||||
return -ENOMEM;
|
||||
|
||||
return osc_context_outro(TAKE_PTR(seq), id, ret_seq, ret_context_id);
|
||||
}
|
||||
|
||||
int osc_context_open_chpriv(const char *target_user, char **ret_seq, sd_id128_t *ret_context_id) {
|
||||
int r;
|
||||
|
||||
assert(target_user);
|
||||
|
||||
_cleanup_free_ char *seq = NULL;
|
||||
sd_id128_t id = SD_ID128_NULL;
|
||||
r = osc_context_intro(&seq, ret_context_id ? &id : NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (is_this_me(target_user) > 0) {
|
||||
if (!strextend(&seq, ";type=subcontext" ANSI_ST))
|
||||
return -ENOMEM;
|
||||
} else if (STR_IN_SET(target_user, "root", "0")) {
|
||||
if (!strextend(&seq, ";type=elevate" ANSI_ST))
|
||||
return -ENOMEM;
|
||||
} else {
|
||||
r = strextend_escaped(&seq, ";targetuser=", target_user);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!strextend(&seq, ";type=chpriv" ANSI_ST))
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return osc_context_outro(TAKE_PTR(seq), id, ret_seq, ret_context_id);
|
||||
}
|
||||
|
||||
int osc_context_open_session(const char *user, const char *session_id, char **ret_seq, sd_id128_t *ret_context_id) {
|
||||
int r;
|
||||
|
||||
_cleanup_free_ char *seq = NULL;
|
||||
sd_id128_t id = SD_ID128_NULL;
|
||||
r = osc_context_intro(&seq, ret_context_id ? &id : NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (user) {
|
||||
r = strextend_escaped(&seq, ";targetuser=", user);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (session_id) {
|
||||
r = strextend_escaped(&seq, ";sessionid=", session_id);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (!strextend(&seq, ";type=session" ANSI_ST))
|
||||
return -ENOMEM;
|
||||
|
||||
return osc_context_outro(TAKE_PTR(seq), id, ret_seq, ret_context_id);
|
||||
}
|
||||
|
||||
int osc_context_open_service(const char *unit, sd_id128_t invocation_id, char **ret_seq) {
|
||||
int r;
|
||||
|
||||
sd_id128_t id = SD_ID128_NULL;
|
||||
r = osc_context_id_from_invocation_id(invocation_id, &id);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
_cleanup_free_ char *seq = NULL;
|
||||
r = osc_context_intro_raw(id, &seq);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (unit) {
|
||||
r = strextend_escaped(&seq, ";servicename=", unit);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
r = strextendf(&seq, ";invocationid=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(invocation_id));
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!strextend(&seq, ";type=service" ANSI_ST))
|
||||
return -ENOMEM;
|
||||
|
||||
return osc_context_outro(TAKE_PTR(seq), id, ret_seq, /* ret_context_id= */ NULL);
|
||||
}
|
||||
|
||||
int osc_context_close(sd_id128_t id, char **ret_seq) {
|
||||
|
||||
if (sd_id128_is_null(id)) {
|
||||
/* nil uuid: no session opened */
|
||||
if (ret_seq)
|
||||
*ret_seq = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (sd_id128_is_allf(id)) /* max uuid: default session opened */
|
||||
osc_context_default_id(&id);
|
||||
|
||||
_cleanup_free_ char *seq = NULL;
|
||||
if (asprintf(&seq, ANSI_OSC "3008;end=" SD_ID128_FORMAT_STR ANSI_ST, SD_ID128_FORMAT_VAL(id)) < 0)
|
||||
return -ENOMEM;
|
||||
|
||||
return osc_context_outro(TAKE_PTR(seq), SD_ID128_NULL, ret_seq, /* ret_context_id= */ NULL);
|
||||
}
|
||||
|
||||
int osc_context_id_from_invocation_id(sd_id128_t id, sd_id128_t *ret) {
|
||||
static const sd_id128_t app_id = SD_ID128_MAKE(5d,63,a5,8d,96,fd,45,d0,a0,f0,63,50,fc,d8,9a,cd);
|
||||
|
||||
assert(ret);
|
||||
|
||||
/* Hash the context id from the invocation id, so that we don't have to store the context id (as long
|
||||
* as we have the invocation ID). We don't use the invocation ID literally in order not to make it too
|
||||
* easy to cancel our service context because the invocation ID is reported literally via the
|
||||
* $INVOCATION_ID env var. */
|
||||
return sd_id128_get_app_specific(id, app_id, ret);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "sd-id128.h"
|
||||
|
||||
#include "macro.h"
|
||||
|
||||
int osc_context_open_boot(char **ret_seq);
|
||||
int osc_context_open_container(const char *name, char **ret_seq, sd_id128_t *ret_context_id);
|
||||
int osc_context_open_vm(const char *name, char **ret_seq, sd_id128_t *ret_context_id);
|
||||
int osc_context_open_chpriv(const char *target_user, char **ret_seq, sd_id128_t *ret_context_id);
|
||||
int osc_context_open_session(const char *user, const char *session_id, char **ret_seq, sd_id128_t *ret_context_id);
|
||||
int osc_context_open_service(const char *unit, sd_id128_t invocation_id, char **ret_seq);
|
||||
int osc_context_close(sd_id128_t id, char **ret_seq);
|
||||
|
||||
static inline void osc_context_closep(sd_id128_t *context_id) {
|
||||
(void) osc_context_close(*ASSERT_PTR(context_id), NULL);
|
||||
}
|
||||
|
||||
int osc_context_id_from_invocation_id(sd_id128_t id, sd_id128_t *ret);
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "alloc-util.h"
|
||||
#include "bus-internal.h"
|
||||
#include "errno-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "format-util.h"
|
||||
#include "macro.h"
|
||||
#include "pam-util.h"
|
||||
@@ -249,6 +250,20 @@ void pam_cleanup_free(pam_handle_t *handle, void *data, int error_status) {
|
||||
free(data);
|
||||
}
|
||||
|
||||
void pam_cleanup_close(pam_handle_t *handle, void *data, int error_status) {
|
||||
|
||||
/* A generic destructor for pam_set_data() that just closes the specified fd.
|
||||
*
|
||||
* As per pam_set_data() docs: the PAM_DATA_SILENT indicates whether we are called in the forked off
|
||||
* payload child of the new session. However, all file descriptors are most likely already closed
|
||||
* there (that's what /bin/login does after all), hence let's simply turn this into a NOP in the
|
||||
* child, and only close the fd in the parent. */
|
||||
if (FLAGS_SET(error_status, PAM_DATA_SILENT))
|
||||
return;
|
||||
|
||||
safe_close(PTR_TO_FD(data));
|
||||
}
|
||||
|
||||
int pam_get_item_many_internal(pam_handle_t *handle, ...) {
|
||||
va_list ap;
|
||||
int r;
|
||||
|
||||
@@ -42,6 +42,7 @@ int pam_release_bus_connection(pam_handle_t *handle, const char *module_name);
|
||||
int pam_get_bus_data(pam_handle_t *handle, const char *module_name, PamBusData **ret);
|
||||
|
||||
void pam_cleanup_free(pam_handle_t *handle, void *data, int error_status);
|
||||
void pam_cleanup_close(pam_handle_t *handle, void *data, int error_status);
|
||||
|
||||
int pam_get_item_many_internal(pam_handle_t *handle, ...);
|
||||
#define pam_get_item_many(handle, ...) pam_get_item_many_internal(handle, __VA_ARGS__, -1)
|
||||
|
||||
@@ -137,6 +137,7 @@ simple_tests += files(
|
||||
'test-open-file.c',
|
||||
'test-ordered-set.c',
|
||||
'test-os-util.c',
|
||||
'test-osc-context.c',
|
||||
'test-parse-argument.c',
|
||||
'test-parse-helpers.c',
|
||||
'test-path-lookup.c',
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "hexdecoct.h"
|
||||
#include "osc-context.h"
|
||||
#include "tests.h"
|
||||
#include "user-util.h"
|
||||
|
||||
TEST(osc) {
|
||||
_cleanup_free_ char *seq = NULL;
|
||||
|
||||
log_info("boot");
|
||||
ASSERT_OK(osc_context_open_boot(&seq));
|
||||
hexdump(/* f = */ NULL, seq, SIZE_MAX);
|
||||
seq = mfree(seq);
|
||||
|
||||
ASSERT_OK(osc_context_close(SD_ID128_ALLF, &seq));
|
||||
hexdump(/* f = */ NULL, seq, SIZE_MAX);
|
||||
seq = mfree(seq);
|
||||
|
||||
log_info("container");
|
||||
sd_id128_t id;
|
||||
ASSERT_OK(osc_context_open_container("foobar", &seq, &id));
|
||||
hexdump(/* f = */ NULL, seq, SIZE_MAX);
|
||||
seq = mfree(seq);
|
||||
|
||||
ASSERT_OK(osc_context_close(id, &seq));
|
||||
hexdump(/* f = */ NULL, seq, SIZE_MAX);
|
||||
seq = mfree(seq);
|
||||
|
||||
log_info("vm");
|
||||
ASSERT_OK(osc_context_open_vm("foobar", &seq, &id));
|
||||
hexdump(/* f = */ NULL, seq, SIZE_MAX);
|
||||
seq = mfree(seq);
|
||||
|
||||
ASSERT_OK(osc_context_close(id, &seq));
|
||||
hexdump(/* f = */ NULL, seq, SIZE_MAX);
|
||||
seq = mfree(seq);
|
||||
|
||||
log_info("chpriv → root");
|
||||
ASSERT_OK(osc_context_open_chpriv("root", &seq, &id));
|
||||
hexdump(/* f = */ NULL, seq, SIZE_MAX);
|
||||
seq = mfree(seq);
|
||||
|
||||
ASSERT_OK(osc_context_close(id, &seq));
|
||||
hexdump(/* f = */ NULL, seq, SIZE_MAX);
|
||||
seq = mfree(seq);
|
||||
|
||||
log_info("chpriv → userxyz");
|
||||
ASSERT_OK(osc_context_open_chpriv("userxyz", &seq, &id));
|
||||
hexdump(/* f = */ NULL, seq, SIZE_MAX);
|
||||
seq = mfree(seq);
|
||||
|
||||
ASSERT_OK(osc_context_close(id, &seq));
|
||||
hexdump(/* f = */ NULL, seq, SIZE_MAX);
|
||||
seq = mfree(seq);
|
||||
|
||||
log_info("chpriv → self");
|
||||
_cleanup_free_ char *self = ASSERT_PTR(getusername_malloc());
|
||||
ASSERT_OK(osc_context_open_chpriv(self, &seq, &id));
|
||||
hexdump(/* f = */ NULL, seq, SIZE_MAX);
|
||||
seq = mfree(seq);
|
||||
|
||||
ASSERT_OK(osc_context_close(id, &seq));
|
||||
hexdump(/* f = */ NULL, seq, SIZE_MAX);
|
||||
seq = mfree(seq);
|
||||
|
||||
log_info("session");
|
||||
ASSERT_OK(osc_context_open_session("foobaruser", "session1", &seq, &id));
|
||||
hexdump(/* f = */ NULL, seq, SIZE_MAX);
|
||||
seq = mfree(seq);
|
||||
|
||||
ASSERT_OK(osc_context_close(id, &seq));
|
||||
hexdump(/* f = */ NULL, seq, SIZE_MAX);
|
||||
seq = mfree(seq);
|
||||
|
||||
log_info("service");
|
||||
sd_id128_t invocation_id;
|
||||
ASSERT_OK(sd_id128_randomize(&invocation_id));
|
||||
ASSERT_OK(osc_context_open_service("getty@tty1.service", invocation_id, &seq));
|
||||
hexdump(/* f = */ NULL, seq, SIZE_MAX);
|
||||
seq = mfree(seq);
|
||||
|
||||
ASSERT_OK(osc_context_id_from_invocation_id(invocation_id, &id));
|
||||
ASSERT_OK(osc_context_close(id, &seq));
|
||||
hexdump(/* f = */ NULL, seq, SIZE_MAX);
|
||||
seq = mfree(seq);
|
||||
}
|
||||
|
||||
DEFINE_TEST_MAIN(LOG_INFO);
|
||||
@@ -290,7 +290,7 @@ TEST(get_color_mode) {
|
||||
TEST(terminal_reset_defensive) {
|
||||
int r;
|
||||
|
||||
r = terminal_reset_defensive(STDOUT_FILENO, /* switch_to_text= */ false);
|
||||
r = terminal_reset_defensive(STDOUT_FILENO, /* flags= */ 0);
|
||||
if (r < 0)
|
||||
log_notice_errno(r, "Failed to reset terminal: %m");
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ static int agent_ask_password_tty(
|
||||
if (tty_fd < 0)
|
||||
return log_error_errno(tty_fd, "Failed to acquire %s: %m", con);
|
||||
|
||||
(void) terminal_reset_defensive_locked(tty_fd, /* switch_to_text= */ true);
|
||||
(void) terminal_reset_defensive_locked(tty_fd, TERMINAL_RESET_SWITCH_TO_TEXT);
|
||||
|
||||
log_info("Starting password query on %s.", con);
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
#include "mkdir.h"
|
||||
#include "namespace-util.h"
|
||||
#include "netif-util.h"
|
||||
#include "osc-context.h"
|
||||
#include "pager.h"
|
||||
#include "parse-argument.h"
|
||||
#include "parse-util.h"
|
||||
@@ -2249,8 +2250,15 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
|
||||
/* Exit when the child exits */
|
||||
(void) event_add_child_pidref(event, NULL, &child_pidref, WEXITED, on_child_exit, NULL);
|
||||
|
||||
_cleanup_(osc_context_closep) sd_id128_t osc_context_id = SD_ID128_NULL;
|
||||
_cleanup_(pty_forward_freep) PTYForward *forward = NULL;
|
||||
if (master >= 0) {
|
||||
if (!terminal_is_dumb()) {
|
||||
r = osc_context_open_vm(arg_machine, /* ret_seq= */ NULL, &osc_context_id);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
r = pty_forward_new(event, master, ptyfwd_flags, &forward);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to create PTY forwarder: %m");
|
||||
|
||||
@@ -399,7 +399,7 @@ grep -q "BAR" /tmp/none-existent-file
|
||||
kill "$PID"
|
||||
|
||||
# Test varlinkctl's --exec fd passing logic properly
|
||||
assert_eq "$(varlinkctl --exec call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Open '{"name": ".host", "mode": "shell", "user": "root", "path": "/bin/bash", "args": ["/bin/bash", "-c", "echo $((7 + 8))"]}' -- bash -c 'read -r -N 2 x <&3 ; echo "$x"')" 15
|
||||
assert_eq "$(varlinkctl --exec call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Open '{"name": ".host", "mode": "shell", "user": "root", "path": "/bin/bash", "args": ["/bin/bash", "-c", "echo $((7 + 8))"], "environment": ["TERM=dumb"]}' -- bash -c 'read -r -N 2 x <&3 ; echo "$x"')" 15
|
||||
|
||||
# test io.systemd.Machine.MapFrom
|
||||
varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.MapFrom '{"name": "long-running", "uid":0, "gid": 0}'
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# This file is part of systemd.
|
||||
#
|
||||
# systemd is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# See tmpfiles.d(5) for details.
|
||||
|
||||
{% if LINK_OSC_CONTEXT_DROPIN %}
|
||||
L$ {{SHELLPROFILEDIR}}/80-systemd-osc-context.sh - - - - {{LIBEXECDIR}}/profile.d/80-systemd-osc-context.sh
|
||||
{% endif %}
|
||||
@@ -31,6 +31,7 @@ endforeach
|
||||
|
||||
in_files = [
|
||||
['20-systemd-shell-extra.conf', 'LINK_SHELL_EXTRA_DROPIN'],
|
||||
['20-systemd-osc-context.conf', 'LINK_OSC_CONTEXT_DROPIN'],
|
||||
['20-systemd-ssh-generator.conf', 'ENABLE_SSH_PROXY_CONFIG'],
|
||||
['20-systemd-stub.conf', 'ENABLE_EFI'],
|
||||
['20-systemd-userdb.conf', 'ENABLE_SSH_USERDB_CONFIG'],
|
||||
|
||||
Reference in New Issue
Block a user