Merge pull request #30547 from poettering/uid0

add new "uid0" command as alternative multi-call interface for systemd-run, as sudo replacement
This commit is contained in:
Lennart Poettering
2023-12-21 22:45:15 +01:00
committed by GitHub
24 changed files with 1363 additions and 63 deletions
+4 -8
View File
@@ -132,6 +132,10 @@ Deprecations and removals:
Features:
* add a new ExecStart= flag that inserts the configured user's shell as first
word in the comand line. (maybe use character '.'). Usecase: tool such as
uid0 can use that to spawn the target user's default shell.
* varlink: figure out how to do docs for our varlink interfaces. Idea: install
interface files augmented with docs in /usr/share/ somewhere. And have
functionality in varlinkctl to merge interface info extracted from binaries
@@ -140,14 +144,6 @@ Features:
* introduce mntid_t, and make it 64bit, as apparently the kernel switched to
64bit mount ids
* Add an alias to systemd-run maybe called "uid0" or so, which tries to mimic
the sudo/su command lines to some level, but is backed by transient services,
and proper security isolate/tty forwarding. This would then allow us to run
systems with NNP turned on reasonably nicely. To make this extra nice and
pretty, in ptyfwd rewrite every NL we pass through so that it resets the bg
color to some reddish tone, and erase the whole coming line first, so that
the background color indicates when operating with privileges and when not.
* use udev rule networkd ownership property to take ownership of network
interfaces nspawn creates
+1
View File
@@ -1257,6 +1257,7 @@ manpages = [
''],
['udev_new', '3', ['udev_ref', 'udev_unref'], ''],
['udevadm', '8', [], ''],
['uid0', '1', [], ''],
['ukify', '1', [], 'ENABLE_UKIFY'],
['user@.service',
'5',
+34 -5
View File
@@ -176,11 +176,14 @@
<term><option>--slice-inherit</option></term>
<listitem><para>Make the new <filename>.service</filename> or <filename>.scope</filename> unit part
of the inherited slice. This option can be combined with <option>--slice=</option>.</para>
of the slice the <command>systemd-run</command> itself has been invoked in. This option may be
combined with <option>--slice=</option>, in which case the slice specified via
<option>--slice=</option> is placed within the slice the <command>systemd-run</command> command is
invoked in.</para>
<para>An inherited slice is located within <command>systemd-run</command> slice. Example: if
<command>systemd-run</command> slice is <filename>foo.slice</filename>, and the
<option>--slice=</option> argument is <filename>bar</filename>, the unit will be placed under the
<para>Example: consider <command>systemd-run</command> being invoked in the slice
<filename>foo.slice</filename>, and the <option>--slice=</option> argument is
<filename>bar</filename>. The unit will then be placed under
<filename>foo-bar.slice</filename>.</para>
<xi:include href="version-info.xml" xpointer="v246"/>
@@ -485,6 +488,31 @@
<xi:include href="version-info.xml" xpointer="v236"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--ignore-failure</option></term>
<listitem><para>By default, if the specified command fails the invoked unit will be marked failed
(though possibly still unloaded, see <option>--collect=</option>, above), and this is reported in the
logs. If this switch is specified this is suppressed and any non-success exit status/code of the
command is treated as success.</para>
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--background=<replaceable>COLOR</replaceable></option></term>
<listitem><para>Change the terminal background color to the specified ANSI color as long as the
session lasts. The color specified should be an ANSI X3.64 SGR background color, i.e. strings such as
<literal>40</literal>, <literal>41</literal>, …, <literal>47</literal>, <literal>48;2;…</literal>,
<literal>48;5;…</literal>. See <ulink
url="https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters">ANSI
Escape Code (Wikipedia)</ulink> for details.</para>
<xi:include href="version-info.xml" xpointer="v256"/>
</listitem>
</varlistentry>
<xi:include href="user-system-options.xml" xpointer="user" />
<xi:include href="user-system-options.xml" xpointer="system" />
<xi:include href="user-system-options.xml" xpointer="host" />
@@ -677,7 +705,8 @@ $ systemd-run --user --wait -p SuccessExitStatus=SIGUSR1 --expand-environment=no
<citerefentry><refentrytitle>systemd.resource-control</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd.timer</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-mount</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>machinectl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
<citerefentry><refentrytitle>machinectl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>uid0</refentrytitle><manvolnum>1</manvolnum></citerefentry>
</para>
</refsect1>
+232
View File
@@ -0,0 +1,232 @@
<?xml version='1.0'?>
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
<refentry id="uid0"
xmlns:xi="http://www.w3.org/2001/XInclude">
<refentryinfo>
<title>uid0</title>
<productname>systemd</productname>
</refentryinfo>
<refmeta>
<refentrytitle>uid0</refentrytitle>
<manvolnum>1</manvolnum>
</refmeta>
<refnamediv>
<refname>uid0</refname>
<refpurpose>Elevate privileges</refpurpose>
</refnamediv>
<refsynopsisdiv>
<cmdsynopsis>
<command>uid0</command>
<arg choice="opt" rep="repeat">OPTIONS</arg>
<arg choice="opt" rep="repeat">COMMAND</arg>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para><command>uid0</command> may be used to temporarily and interactively acquire elavated or different
privileges. It serves a similar purpose as <citerefentry
project='man-pages'><refentrytitle>sudo</refentrytitle><manvolnum>8</manvolnum></citerefentry>, but
operates differently in a couple of key areas:</para>
<itemizedlist>
<listitem><para>No execution or security context credentials are inherited from the caller into the
invoked commands, as they are invoked from a fresh, isolated service forked off the service
manager.</para></listitem>
<listitem><para>Authentication takes place via <ulink
url="https://www.freedesktop.org/wiki/Software/polkit">polkit</ulink>, thus isolating the
authentication prompt from the terminal (if possible).</para></listitem>
<listitem><para>An independent pseudo-tty is allocated for the invoked command, detaching its lifecycle and
isolating it for security.</para></listitem>
<listitem><para>No SetUID/SetGID file access bit functionality is used for the implementation.</para></listitem>
</itemizedlist>
<para>Altogether this should provide a safer and more robust alternative to the <command>sudo</command>
mechanism, in particular in OS environments where SetUID/SetGID support is not available (for example by
setting the <varname>NoNewPrivileges=</varname> variable in
<citerefentry><refentrytitle>systemd-system.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>).</para>
<para>Any session invoked via <command>uid0</command> will run through the
<literal>systemd-uid0</literal> PAM stack.</para>
<para>Note that <command>uid0</command> is implemented as an alternative multi-call invocation of
<citerefentry><refentrytitle>systemd-run</refentrytitle><manvolnum>1</manvolnum></citerefentry>.</para>
</refsect1>
<refsect1>
<title>Options</title>
<para>The following options are understood:</para>
<variablelist>
<varlistentry>
<term><option>--no-ask-password</option></term>
<listitem><para>Do not query the user for authentication for privileged operations.</para>
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--unit=</option></term>
<listitem><para>Use this unit name instead of an automatically generated one.</para>
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--property=</option></term>
<listitem><para>Sets a property on the service unit that is created. This option takes an assignment
in the same format as
<citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>'s
<command>set-property</command> command.</para>
<xi:include href="version-info.xml" xpointer="v256"/>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--description=</option></term>
<listitem><para>Provide a description for the service unit that is invoked. If not specified,
the command itself will be used as a description. See <varname>Description=</varname> in
<citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
</para>
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--slice=</option></term>
<listitem><para>Make the new <filename>.service</filename> unit part of the specified slice, instead
of <filename>user.slice</filename>.</para>
<xi:include href="version-info.xml" xpointer="v256"/>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--slice-inherit</option></term>
<listitem><para>Make the new <filename>.service</filename> unit part of the slice the
<command>uid0</command> itself has been invoked in. This option may be combined with
<option>--slice=</option>, in which case the slice specified via <option>--slice=</option> is placed
within the slice the <command>uid0</command> command is invoked in.</para>
<para>Example: consider <command>uid0</command> being invoked in the slice
<filename>foo.slice</filename>, and the <option>--slice=</option> argument is
<filename>bar</filename>. The unit will then be placed under
<filename>foo-bar.slice</filename>.</para>
<xi:include href="version-info.xml" xpointer="v256"/>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--user=</option></term>
<term><option>-u</option></term>
<term><option>--group=</option></term>
<term><option>-g</option></term>
<listitem><para>Switches to the specified user/group instead of root.</para>
<xi:include href="version-info.xml" xpointer="v256"/>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--nice=</option></term>
<listitem><para>Runs the invoked session with the specified nice level.</para>
<xi:include href="version-info.xml" xpointer="v256"/>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--chdir=</option></term>
<term><option>-D</option></term>
<listitem><para>Runs the invoked session with the specified working directory. If not specified
defaults to the client's current working directory if switching to the root user, or the target
user's home directory otherwise.</para>
<xi:include href="version-info.xml" xpointer="v256"/>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--setenv=<replaceable>NAME</replaceable>[=<replaceable>VALUE</replaceable>]</option></term>
<listitem><para>Runs the invoked session with the specified environment variable set. This parameter
may be used more than once to set multiple variables. When <literal>=</literal> and
<replaceable>VALUE</replaceable> are omitted, the value of the variable with the same name in the
invoking environment will be used.</para>
<xi:include href="version-info.xml" xpointer="v256"/>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--background=<replaceable>COLOR</replaceable></option></term>
<listitem><para>Change the terminal background color to the specified ANSI color as long as the
session lasts. If not specified, the background will be tinted in a reddish tone when operating as
root, and in a yellowish tone when operating under another UID, as reminder of the changed
privileges. The color specified should be an ANSI X3.64 SGR background color, i.e. strings such as
<literal>40</literal>, <literal>41</literal>, …, <literal>47</literal>, <literal>48;2;…</literal>,
<literal>48;5;…</literal>. See <ulink
url="https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters">ANSI
Escape Code (Wikipedia)</ulink> for details. Set to an empty string to disable.</para>
<para>Example: <literal>--background=44</literal> for a blue background.</para>
<xi:include href="version-info.xml" xpointer="v256"/>
</listitem>
</varlistentry>
<xi:include href="user-system-options.xml" xpointer="machine" />
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />
</variablelist>
<para>All command line arguments after the first non-option argument become part of the command line of
the launched process. If no command line is specified an interactive shell is invoked. The shell to
invoke may be controlled via <option>--setenv=SHELL=…</option> and currently defaults to the
<emphasis>originating user's</emphasis> shell (i.e. not the target user's!) if operating locally, or
<filename>/bin/sh</filename> when operating with <option>--machine=</option>.</para>
</refsect1>
<refsect1>
<title>Exit status</title>
<para>On success, 0 is returned. If <command>uid0</command> failed to start the session or the specified command fails, a
non-zero return value will be returned.</para>
</refsect1>
<refsect1>
<title>See Also</title>
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-run</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry project='man-pages'><refentrytitle>sudo</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>machinectl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
</para>
</refsect1>
</refentry>
+1 -1
View File
@@ -70,7 +70,7 @@ if [ ! -f "$BUILDDIR"/build.ninja ]; then
-D tests=unsafe
-D slow-tests="${SLOW_TESTS:-false}"
-D create-log-dirs=false
-D pamconfdir=no
-D pamconfdir=/usr/lib/pam.d/
-D utmp=true
-D hibernate=true
-D ldconfig=true
+29
View File
@@ -458,6 +458,35 @@ int strv_env_assign(char ***l, const char *key, const char *value) {
return strv_env_replace_consume(l, p);
}
int strv_env_assignf(char ***l, const char *key, const char *valuef, ...) {
int r;
assert(l);
assert(key);
if (!env_name_is_valid(key))
return -EINVAL;
if (!valuef) {
strv_env_unset(*l, key);
return 0;
}
_cleanup_free_ char *value = NULL;
va_list ap;
va_start(ap, valuef);
r = vasprintf(&value, valuef, ap);
va_end(ap);
if (r < 0)
return -ENOMEM;
char *p = strjoin(key, "=", value);
if (!p)
return -ENOMEM;
return strv_env_replace_consume(l, p);
}
int _strv_env_assign_many(char ***l, ...) {
va_list ap;
int r;
+1
View File
@@ -49,6 +49,7 @@ int strv_env_replace_consume(char ***l, char *p); /* In place ... */
int strv_env_replace_strdup(char ***l, const char *assignment);
int strv_env_replace_strdup_passthrough(char ***l, const char *assignment);
int strv_env_assign(char ***l, const char *key, const char *value);
int strv_env_assignf(char ***l, const char *key, const char *valuef, ...) _printf_(3, 4);
int _strv_env_assign_many(char ***l, ...) _sentinel_;
#define strv_env_assign_many(l, ...) _strv_env_assign_many(l, __VA_ARGS__, NULL)
+216
View File
@@ -27,6 +27,7 @@
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
#include "hexdecoct.h"
#include "inotify-util.h"
#include "io-util.h"
#include "log.h"
@@ -1551,3 +1552,218 @@ int set_terminal_cursor_position(int fd, unsigned int row, unsigned int column)
return 0;
}
void termios_disable_echo(struct termios *termios) {
assert(termios);
termios->c_lflag &= ~(ICANON|ECHO);
termios->c_cc[VMIN] = 1;
termios->c_cc[VTIME] = 0;
}
typedef enum BackgroundColorState {
BACKGROUND_TEXT,
BACKGROUND_ESCAPE,
BACKGROUND_BRACKET,
BACKGROUND_FIRST_ONE,
BACKGROUND_SECOND_ONE,
BACKGROUND_SEMICOLON,
BACKGROUND_R,
BACKGROUND_G,
BACKGROUND_B,
BACKGROUND_RED,
BACKGROUND_GREEN,
BACKGROUND_BLUE,
} BackgroundColorState;
typedef struct BackgroundColorContext {
BackgroundColorState state;
uint32_t red, green, blue;
unsigned red_bits, green_bits, blue_bits;
} BackgroundColorContext;
static int scan_background_color_response(
BackgroundColorContext *context,
const char *buf,
size_t size) {
assert(context);
assert(buf || size == 0);
for (size_t i = 0; i < size; i++) {
char c = buf[i];
switch (context->state) {
case BACKGROUND_TEXT:
context->state = c == '\x1B' ? BACKGROUND_ESCAPE : BACKGROUND_TEXT;
break;
case BACKGROUND_ESCAPE:
context->state = c == ']' ? BACKGROUND_BRACKET : BACKGROUND_TEXT;
break;
case BACKGROUND_BRACKET:
context->state = c == '1' ? BACKGROUND_FIRST_ONE : BACKGROUND_TEXT;
break;
case BACKGROUND_FIRST_ONE:
context->state = c == '1' ? BACKGROUND_SECOND_ONE : BACKGROUND_TEXT;
break;
case BACKGROUND_SECOND_ONE:
context->state = c == ';' ? BACKGROUND_SEMICOLON : BACKGROUND_TEXT;
break;
case BACKGROUND_SEMICOLON:
context->state = c == 'r' ? BACKGROUND_R : BACKGROUND_TEXT;
break;
case BACKGROUND_R:
context->state = c == 'g' ? BACKGROUND_G : BACKGROUND_TEXT;
break;
case BACKGROUND_G:
context->state = c == 'b' ? BACKGROUND_B : BACKGROUND_TEXT;
break;
case BACKGROUND_B:
context->state = c == ':' ? BACKGROUND_RED : BACKGROUND_TEXT;
break;
case BACKGROUND_RED:
if (c == '/')
context->state = context->red_bits > 0 ? BACKGROUND_GREEN : BACKGROUND_TEXT;
else {
int d = unhexchar(c);
if (d < 0 || context->red_bits >= sizeof(context->red)*8)
context->state = BACKGROUND_TEXT;
else {
context->red = (context->red << 4) | d;
context->red_bits += 4;
}
}
break;
case BACKGROUND_GREEN:
if (c == '/')
context->state = context->green_bits > 0 ? BACKGROUND_BLUE : BACKGROUND_TEXT;
else {
int d = unhexchar(c);
if (d < 0 || context->green_bits >= sizeof(context->green)*8)
context->state = BACKGROUND_TEXT;
else {
context->green = (context->green << 4) | d;
context->green_bits += 4;
}
}
break;
case BACKGROUND_BLUE:
if (c == '\x07') {
if (context->blue_bits > 0)
return 1; /* success! */
context->state = BACKGROUND_TEXT;
} else {
int d = unhexchar(c);
if (d < 0 || context->blue_bits >= sizeof(context->blue)*8)
context->state = BACKGROUND_TEXT;
else {
context->blue = (context->blue << 4) | d;
context->blue_bits += 4;
}
}
break;
}
/* Reset any colors we might have picked up */
if (context->state == BACKGROUND_TEXT) {
/* reset color */
context->red = context->green = context->blue = 0;
context->red_bits = context->green_bits = context->blue_bits = 0;
}
}
return 0; /* all good, but not enough data yet */
}
int get_default_background_color(double *ret_red, double *ret_green, double *ret_blue) {
int r;
assert(ret_red);
assert(ret_green);
assert(ret_blue);
if (!colors_enabled())
return -EOPNOTSUPP;
if (isatty(STDOUT_FILENO) < 1 || isatty(STDIN_FILENO) < 1)
return -EOPNOTSUPP;
if (streq_ptr(getenv("TERM"), "linux")) {
/* Linux console is black */
*ret_red = *ret_green = *ret_blue = 0.0;
return 0;
}
struct termios old_termios;
if (tcgetattr(STDIN_FILENO, &old_termios) < 0)
return -errno;
struct termios new_termios = old_termios;
termios_disable_echo(&new_termios);
if (tcsetattr(STDOUT_FILENO, TCSADRAIN, &new_termios) < 0)
return -errno;
r = loop_write(STDOUT_FILENO, "\x1B]11;?\x07", SIZE_MAX);
if (r < 0)
goto finish;
usec_t end = usec_add(now(CLOCK_MONOTONIC), 100 * USEC_PER_MSEC);
char buf[256];
size_t buf_full = 0;
BackgroundColorContext context = {};
for (;;) {
usec_t n = now(CLOCK_MONOTONIC);
if (n >= end) {
r = -EOPNOTSUPP;
goto finish;
}
r = fd_wait_for_event(STDIN_FILENO, POLLIN, usec_sub_unsigned(end, n));
if (r < 0)
goto finish;
ssize_t l;
l = read(STDIN_FILENO, buf, sizeof(buf) - buf_full);
if (l < 0) {
r = -errno;
goto finish;
}
buf_full += l;
assert(buf_full <= sizeof(buf));
r = scan_background_color_response(&context, buf, buf_full);
if (r < 0)
goto finish;
if (r > 0) {
assert(context.red_bits > 0);
*ret_red = (double) context.red / ((UINT64_C(1) << context.red_bits) - 1);
assert(context.green_bits > 0);
*ret_green = (double) context.green / ((UINT64_C(1) << context.green_bits) - 1);
assert(context.blue_bits > 0);
*ret_blue = (double) context.blue / ((UINT64_C(1) << context.blue_bits) - 1);
r = 0;
goto finish;
}
}
finish:
(void) tcsetattr(STDOUT_FILENO, TCSADRAIN, &old_termios);
return r;
}
+5 -1
View File
@@ -6,6 +6,7 @@
#include <stdio.h>
#include <syslog.h>
#include <sys/types.h>
#include <termios.h>
#include "macro.h"
#include "time-util.h"
@@ -168,7 +169,6 @@ bool underline_enabled(void);
bool dev_console_colors_enabled(void);
static inline bool colors_enabled(void) {
/* Returns true if colors are considered supported on our stdout. */
return get_color_mode() != COLOR_OFF;
}
@@ -286,3 +286,7 @@ static inline const char* ansi_highlight_green_red(bool b) {
/* This assumes there is a 'tty' group */
#define TTY_MODE 0620
void termios_disable_echo(struct termios *termios);
int get_default_background_color(double *ret_red, double *ret_green, double *ret_blue);
+1 -34
View File
@@ -11,6 +11,7 @@
#include "blockdev-util.h"
#include "build.h"
#include "chase.h"
#include "color-util.h"
#include "conf-files.h"
#include "efi-api.h"
#include "env-util.h"
@@ -1932,40 +1933,6 @@ static int event_log_map_components(EventLog *el) {
return event_log_validate_fully_recognized(el);
}
static void hsv_to_rgb(
double h, double s, double v,
uint8_t* ret_r, uint8_t *ret_g, uint8_t *ret_b) {
double c, x, m, r, g, b;
assert(s >= 0 && s <= 100);
assert(v >= 0 && v <= 100);
assert(ret_r);
assert(ret_g);
assert(ret_b);
c = (s / 100.0) * (v / 100.0);
x = c * (1 - fabs(fmod(h / 60.0, 2) - 1));
m = (v / 100) - c;
if (h >= 0 && h < 60)
r = c, g = x, b = 0.0;
else if (h >= 60 && h < 120)
r = x, g = c, b = 0.0;
else if (h >= 120 && h < 180)
r = 0.0, g = c, b = x;
else if (h >= 180 && h < 240)
r = 0.0, g = x, b = c;
else if (h >= 240 && h < 300)
r = x, g = 0.0, b = c;
else
r = c, g = 0.0, b = x;
*ret_r = (uint8_t) ((r + m) * 255);
*ret_g = (uint8_t) ((g + m) * 255);
*ret_b = (uint8_t) ((b + m) * 255);
}
#define ANSI_TRUE_COLOR_MAX (7U + 3U + 1U + 3U + 1U + 3U + 2U)
static const char *ansi_true_color(uint8_t r, uint8_t g, uint8_t b, char ret[static ANSI_TRUE_COLOR_MAX]) {
+14
View File
@@ -7,3 +7,17 @@ executables += [
'sources' : files('run.c'),
},
]
install_emptydir(bindir)
meson.add_install_script(sh, '-c',
ln_s.format(bindir / 'systemd-run',
bindir / 'uid0'))
custom_target(
'systemd-uid0',
input : 'systemd-uid0.in',
output : 'systemd-uid0',
command : [jinja2_cmdline, '@INPUT@', '@OUTPUT@'],
install : pamconfdir != 'no',
install_dir : pamconfdir)
+336 -6
View File
@@ -17,6 +17,7 @@
#include "bus-unit-util.h"
#include "bus-wait-for-jobs.h"
#include "calendarspec.h"
#include "color-util.h"
#include "env-util.h"
#include "escape.h"
#include "exit-status.h"
@@ -73,6 +74,9 @@ static bool arg_aggressive_gc = false;
static char *arg_working_directory = NULL;
static bool arg_shell = false;
static char **arg_cmdline = NULL;
static char *arg_exec_path = NULL;
static bool arg_ignore_failure = false;
static char *arg_background = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_description, freep);
STATIC_DESTRUCTOR_REGISTER(arg_environment, strv_freep);
@@ -82,6 +86,8 @@ STATIC_DESTRUCTOR_REGISTER(arg_socket_property, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_timer_property, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_working_directory, freep);
STATIC_DESTRUCTOR_REGISTER(arg_cmdline, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_exec_path, freep);
STATIC_DESTRUCTOR_REGISTER(arg_background, freep);
static int help(void) {
_cleanup_free_ char *link = NULL;
@@ -104,7 +110,7 @@ static int help(void) {
" -p --property=NAME=VALUE Set service or scope unit property\n"
" --description=TEXT Description for unit\n"
" --slice=SLICE Run in the specified slice\n"
" --slice-inherit Inherit the slice\n"
" --slice-inherit Inherit the slice from the caller\n"
" --expand-environment=BOOL Control expansion of environment variables\n"
" --no-block Do not wait until operation finished\n"
" -r --remain-after-exit Leave service around until explicitly stopped\n"
@@ -123,6 +129,8 @@ static int help(void) {
" -q --quiet Suppress information messages during runtime\n"
" -G --collect Unload unit after it ran, even when failed\n"
" -S --shell Invoke a $SHELL interactively\n"
" --ignore-failure Ignore the exit status of the invoked process\n"
" --background=COLOR Set ANSI color for background\n"
"\n%3$sPath options:%4$s\n"
" --path-property=NAME=VALUE Set path unit property\n"
"\n%3$sSocket options:%4$s\n"
@@ -146,6 +154,40 @@ static int help(void) {
return 0;
}
static int help_sudo_mode(void) {
_cleanup_free_ char *link = NULL;
int r;
r = terminal_urlify_man("uid0", "1", &link);
if (r < 0)
return log_oom();
printf("%s [OPTIONS...] COMMAND [ARGUMENTS...]\n"
"\n%sElevate privileges interactively.%s\n\n"
" -h --help Show this help\n"
" -V --version Show package version\n"
" --no-ask-password Do not prompt for password\n"
" --machine=CONTAINER Operate on local container\n"
" --unit=UNIT Run under the specified unit name\n"
" --property=NAME=VALUE Set service or scope unit property\n"
" --description=TEXT Description for unit\n"
" --slice=SLICE Run in the specified slice\n"
" --slice-inherit Inherit the slice\n"
" -u --user=USER Run as system user\n"
" -g --group=GROUP Run as system group\n"
" --nice=NICE Nice level\n"
" -D --chdir=PATH Set working directory\n"
" --setenv=NAME[=VALUE] Set environment variable\n"
" --background=COLOR Set ANSI color for background\n"
"\nSee the %s for details.\n",
program_invocation_short_name,
ansi_highlight(),
ansi_normal(),
link);
return 0;
}
static int add_timer_property(const char *name, const char *val) {
char *p;
@@ -162,6 +204,18 @@ static int add_timer_property(const char *name, const char *val) {
return 0;
}
static char **make_login_shell_cmdline(const char *shell) {
_cleanup_free_ char *argv0 = NULL;
assert(shell);
argv0 = strjoin("-", shell); /* The - is how shells determine if they shall be consider login shells */
if (!argv0)
return NULL;
return strv_new(argv0);
}
static int parse_argv(int argc, char *argv[]) {
enum {
@@ -194,6 +248,8 @@ static int parse_argv(int argc, char *argv[]) {
ARG_WAIT,
ARG_WORKING_DIRECTORY,
ARG_SHELL,
ARG_IGNORE_FAILURE,
ARG_BACKGROUND,
};
static const struct option options[] = {
@@ -239,6 +295,8 @@ static int parse_argv(int argc, char *argv[]) {
{ "working-directory", required_argument, NULL, ARG_WORKING_DIRECTORY },
{ "same-dir", no_argument, NULL, 'd' },
{ "shell", no_argument, NULL, 'S' },
{ "ignore-failure", no_argument, NULL, ARG_IGNORE_FAILURE },
{ "background", no_argument, NULL, ARG_BACKGROUND },
{},
};
@@ -282,7 +340,7 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ARG_DESCRIPTION:
r = free_and_strdup(&arg_description, optarg);
r = free_and_strdup_warn(&arg_description, optarg);
if (r < 0)
return r;
break;
@@ -524,6 +582,16 @@ static int parse_argv(int argc, char *argv[]) {
arg_shell = true;
break;
case ARG_IGNORE_FAILURE:
arg_ignore_failure = true;
break;
case ARG_BACKGROUND:
r = free_and_strdup_warn(&arg_background, optarg);
if (r < 0)
return r;
break;
case '?':
return -EINVAL;
@@ -651,6 +719,259 @@ static int parse_argv(int argc, char *argv[]) {
return 1;
}
static int parse_argv_sudo_mode(int argc, char *argv[]) {
enum {
ARG_NO_ASK_PASSWORD = 0x100,
ARG_HOST,
ARG_MACHINE,
ARG_UNIT,
ARG_PROPERTY,
ARG_DESCRIPTION,
ARG_SLICE,
ARG_SLICE_INHERIT,
ARG_NICE,
ARG_SETENV,
ARG_BACKGROUND,
};
/* If invoked as "uid0" binary, let's expose a more sudo-like interface. We add various extensions
* though (but limit the extension to long options). */
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, 'V' },
{ "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
{ "machine", required_argument, NULL, ARG_MACHINE },
{ "unit", required_argument, NULL, ARG_UNIT },
{ "property", required_argument, NULL, ARG_PROPERTY },
{ "description", required_argument, NULL, ARG_DESCRIPTION },
{ "slice", required_argument, NULL, ARG_SLICE },
{ "slice-inherit", no_argument, NULL, ARG_SLICE_INHERIT },
{ "user", required_argument, NULL, 'u' },
{ "group", required_argument, NULL, 'g' },
{ "nice", required_argument, NULL, ARG_NICE },
{ "chdir", required_argument, NULL, 'D' },
{ "setenv", required_argument, NULL, ARG_SETENV },
{ "background", required_argument, NULL, ARG_BACKGROUND },
{},
};
int r, c;
assert(argc >= 0);
assert(argv);
/* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
* that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
optind = 0;
while ((c = getopt_long(argc, argv, "+hVu:g:D:", options, NULL)) >= 0)
switch (c) {
case 'h':
return help_sudo_mode();
case 'V':
return version();
case ARG_NO_ASK_PASSWORD:
arg_ask_password = false;
break;
case ARG_MACHINE:
arg_transport = BUS_TRANSPORT_MACHINE;
arg_host = optarg;
break;
case ARG_UNIT:
arg_unit = optarg;
break;
case ARG_PROPERTY:
if (strv_extend(&arg_property, optarg) < 0)
return log_oom();
break;
case ARG_DESCRIPTION:
r = free_and_strdup_warn(&arg_description, optarg);
if (r < 0)
return r;
break;
case ARG_SLICE:
arg_slice = optarg;
break;
case ARG_SLICE_INHERIT:
arg_slice_inherit = true;
break;
case 'u':
arg_exec_user = optarg;
break;
case 'g':
arg_exec_group = optarg;
break;
case ARG_NICE:
r = parse_nice(optarg, &arg_nice);
if (r < 0)
return log_error_errno(r, "Failed to parse nice value: %s", optarg);
arg_nice_set = true;
break;
case 'D':
r = parse_path_argument(optarg, true, &arg_working_directory);
if (r < 0)
return r;
break;
case ARG_SETENV:
r = strv_env_replace_strdup_passthrough(&arg_environment, optarg);
if (r < 0)
return log_error_errno(r, "Cannot assign environment variable %s: %m", optarg);
break;
case ARG_BACKGROUND:
r = free_and_strdup_warn(&arg_background, optarg);
if (r < 0)
return r;
break;
case '?':
return -EINVAL;
default:
assert_not_reached();
}
if (!arg_working_directory) {
if (arg_exec_user) {
/* When switching to a specific user, also switch to its home directory. */
arg_working_directory = strdup("~");
if (!arg_working_directory)
return log_oom();
} else {
/* When switching to root without this being specified, then stay in the current directory */
r = safe_getcwd(&arg_working_directory);
if (r < 0)
return log_error_errno(r, "Failed to get current working directory: %m");
}
}
arg_service_type = "exec";
arg_quiet = true;
arg_wait = true;
arg_aggressive_gc = true;
arg_stdio = isatty(STDIN_FILENO) && isatty(STDOUT_FILENO) && isatty(STDERR_FILENO) ? ARG_STDIO_PTY : ARG_STDIO_DIRECT;
arg_expand_environment = false;
arg_send_sighup = true;
_cleanup_strv_free_ char **l = NULL;
if (argc > optind)
l = strv_copy(argv + optind);
else {
const char *e;
e = strv_env_get(arg_environment, "SHELL");
if (e)
arg_exec_path = strdup(e);
else {
if (arg_transport == BUS_TRANSPORT_LOCAL) {
r = get_shell(&arg_exec_path);
if (r < 0)
return log_error_errno(r, "Failed to determine shell: %m");
} else
arg_exec_path = strdup("/bin/sh");
}
if (!arg_exec_path)
return log_oom();
l = make_login_shell_cmdline(arg_exec_path);
}
if (!l)
return log_oom();
strv_free_and_replace(arg_cmdline, l);
if (!arg_slice) {
arg_slice = strdup("user.slice");
if (!arg_slice)
return log_oom();
}
_cleanup_free_ char *un = NULL;
un = getusername_malloc();
if (!un)
return log_oom();
/* Set a bunch of environment variables in a roughly sudo-compatible way */
r = strv_env_assign(&arg_environment, "SUDO_USER", un);
if (r < 0)
return log_error_errno(r, "Failed to set $SUDO_USER environment variable: %m");
r = strv_env_assignf(&arg_environment, "SUDO_UID", UID_FMT, getuid());
if (r < 0)
return log_error_errno(r, "Failed to set $SUDO_UID environment variable: %m");
r = strv_env_assignf(&arg_environment, "SUDO_GID", GID_FMT, getgid());
if (r < 0)
return log_error_errno(r, "Failed to set $SUDO_GID environment variable: %m");
if (strv_extendf(&arg_property, "LogExtraFields=ELEVATED_UID=" UID_FMT, getuid()) < 0)
return log_oom();
if (strv_extendf(&arg_property, "LogExtraFields=ELEVATED_GID=" GID_FMT, getgid()) < 0)
return log_oom();
if (strv_extendf(&arg_property, "LogExtraFields=ELEVATED_USER=%s", un) < 0)
return log_oom();
if (strv_extend(&arg_property, "PAMName=systemd-uid0") < 0)
return log_oom();
if (!arg_background && arg_stdio == ARG_STDIO_PTY) {
double red, green, blue;
r = get_default_background_color(&red, &green, &blue);
if (r < 0)
log_debug_errno(r, "Unable to get terminal background color, not tinting background: %m");
else {
double h, s, v;
rgb_to_hsv(red, green, blue, &h, &s, &v);
if (!arg_exec_user || STR_IN_SET(arg_exec_user, "root", "0"))
h = 0; /* red */
else
h = 60 /* yellow */;
if (v > 50) /* If the background is bright, then pull down saturation */
s = 25;
else /* otherwise pump it up */
s = 75;
v = MAX(30, v); /* Make sure we don't hide the color in black */
uint8_t r8, g8, b8;
hsv_to_rgb(h, s, v, &r8, &g8, &b8);
if (asprintf(&arg_background, "48;2;%u;%u;%u", r8, g8, b8) < 0)
return log_oom();
}
}
return 1;
}
static int transient_unit_set_properties(sd_bus_message *m, UnitType t, char **properties) {
int r;
@@ -899,7 +1220,7 @@ static int transient_service_set_properties(sd_bus_message *m, const char *pty_p
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append(m, "s", arg_cmdline[0]);
r = sd_bus_message_append(m, "s", arg_exec_path ?: arg_cmdline[0]);
if (r < 0)
return bus_log_create_error(r);
@@ -910,9 +1231,10 @@ static int transient_service_set_properties(sd_bus_message *m, const char *pty_p
if (use_ex_prop)
r = sd_bus_message_append_strv(
m,
STRV_MAKE(arg_expand_environment > 0 ? NULL : "no-env-expand"));
STRV_MAKE(arg_expand_environment > 0 ? NULL : "no-env-expand",
arg_ignore_failure ? "ignore-failure" : NULL));
else
r = sd_bus_message_append(m, "b", false);
r = sd_bus_message_append(m, "b", arg_ignore_failure);
if (r < 0)
return bus_log_create_error(r);
@@ -1432,6 +1754,9 @@ static int start_transient_service(sd_bus *bus) {
/* Make sure to process any TTY events before we process bus events */
(void) pty_forward_set_priority(c.forward, SD_EVENT_PRIORITY_IMPORTANT);
if (!isempty(arg_background))
(void) pty_forward_set_background_color(c.forward, arg_background);
}
path = unit_dbus_path_from_name(service);
@@ -1899,6 +2224,8 @@ static int start_transient_trigger(sd_bus *bus, const char *suffix) {
}
static bool shall_make_executable_absolute(void) {
if (arg_exec_path)
return false;
if (strv_isempty(arg_cmdline))
return false;
if (arg_transport != BUS_TRANSPORT_LOCAL)
@@ -1919,7 +2246,10 @@ static int run(int argc, char* argv[]) {
log_parse_environment();
log_open();
r = parse_argv(argc, argv);
if (invoked_as(argv, "uid0"))
r = parse_argv_sudo_mode(argc, argv);
else
r = parse_argv(argc, argv);
if (r <= 0)
return r;
+23
View File
@@ -0,0 +1,23 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# This file is part of systemd.
#
# Used by uid0 sessions
{% if ENABLE_HOMED %}
-account sufficient pam_systemd_home.so
{% endif %}
account required pam_unix.so
{% if HAVE_SELINUX %}
session required pam_selinux.so close
session required pam_selinux.so open
{% endif %}
session required pam_loginuid.so
session optional pam_keyinit.so force revoke
session required pam_namespace.so
{% if ENABLE_HOMED %}
-session optional pam_systemd_home.so
{% endif %}
session optional pam_umask.so silent
session optional pam_systemd.so
session required pam_unix.so
+1 -3
View File
@@ -443,9 +443,7 @@ int ask_password_tty(
(void) loop_write(ttyfd, ANSI_NORMAL, SIZE_MAX);
new_termios = old_termios;
new_termios.c_lflag &= ~(ICANON|ECHO);
new_termios.c_cc[VMIN] = 1;
new_termios.c_cc[VTIME] = 0;
termios_disable_echo(&new_termios);
r = RET_NERRNO(tcsetattr(ttyfd, TCSADRAIN, &new_termios));
if (r < 0)
+77
View File
@@ -0,0 +1,77 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <math.h>
#include "color-util.h"
#include "macro.h"
void rgb_to_hsv(double r, double g, double b,
double *ret_h, double *ret_s, double *ret_v) {
assert(r >= 0 && r <= 1);
assert(g >= 0 && g <= 1);
assert(b >= 0 && b <= 1);
assert(ret_h);
assert(ret_s);
assert(ret_v);
double max_color = fmax(r, fmax(g, b));
double min_color = fmin(r, fmin(g, b));
double delta = max_color - min_color;
*ret_v = max_color * 100.0;
if (max_color > 0)
*ret_s = delta / max_color * 100.0;
else {
*ret_s = 0;
*ret_h = NAN;
return;
}
if (delta > 0) {
if (r >= max_color)
*ret_h = 60 * fmod((g - b) / delta, 6);
else if (g >= max_color)
*ret_h = 60 * (((b - r) / delta) + 2);
else if (b >= max_color)
*ret_h = 60 * (((r - g) / delta) + 4);
*ret_h = fmod(*ret_h, 360);
} else
*ret_h = NAN;
}
void hsv_to_rgb(double h, double s, double v,
uint8_t* ret_r, uint8_t *ret_g, uint8_t *ret_b) {
double c, x, m, r, g, b;
assert(s >= 0 && s <= 100);
assert(v >= 0 && v <= 100);
assert(ret_r);
assert(ret_g);
assert(ret_b);
h = fmod(h, 360);
c = (s / 100.0) * (v / 100.0);
x = c * (1 - fabs(fmod(h / 60.0, 2) - 1));
m = (v / 100) - c;
if (h >= 0 && h < 60)
r = c, g = x, b = 0.0;
else if (h >= 60 && h < 120)
r = x, g = c, b = 0.0;
else if (h >= 120 && h < 180)
r = 0.0, g = c, b = x;
else if (h >= 180 && h < 240)
r = 0.0, g = x, b = c;
else if (h >= 240 && h < 300)
r = x, g = 0.0, b = c;
else
r = c, g = 0.0, b = x;
*ret_r = (uint8_t) ((r + m) * 255);
*ret_g = (uint8_t) ((g + m) * 255);
*ret_b = (uint8_t) ((b + m) * 255);
}
+11
View File
@@ -0,0 +1,11 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <inttypes.h>
void rgb_to_hsv(double r, double g, double b,
double *ret_h, double *ret_s, double *ret_v);
void hsv_to_rgb(
double h, double s, double v,
uint8_t* ret_r, uint8_t *ret_g, uint8_t *ret_b);
+1
View File
@@ -39,6 +39,7 @@ shared_sources = files(
'chown-recursive.c',
'clean-ipc.c',
'clock-util.c',
'color-util.c',
'common-signal.c',
'compare-operator.c',
'condition.c',
+274 -5
View File
@@ -18,15 +18,26 @@
#include "alloc-util.h"
#include "errno-util.h"
#include "extract-word.h"
#include "fd-util.h"
#include "io-util.h"
#include "log.h"
#include "macro.h"
#include "ptyfwd.h"
#include "stat-util.h"
#include "strv.h"
#include "terminal-util.h"
#include "time-util.h"
typedef enum AnsiColorState {
ANSI_COLOR_STATE_TEXT,
ANSI_COLOR_STATE_ESC,
ANSI_COLOR_STATE_CSI_SEQUENCE,
ANSI_COLOR_STATE_NEWLINE,
_ANSI_COLOR_STATE_MAX,
_ANSI_COLOR_STATE_INVALID = -EINVAL,
} AnsiColorState;
struct PTYForward {
sd_event *event;
@@ -67,7 +78,8 @@ struct PTYForward {
bool last_char_set:1;
char last_char;
char in_buffer[LINE_MAX], out_buffer[LINE_MAX];
char in_buffer[LINE_MAX], *out_buffer;
size_t out_buffer_size;
size_t in_buffer_full, out_buffer_full;
usec_t escape_timestamp;
@@ -75,6 +87,10 @@ struct PTYForward {
PTYForwardHandler handler;
void *userdata;
char *background_color;
AnsiColorState ansi_color_state;
char *csi_sequence;
};
#define ESCAPE_USEC (1*USEC_PER_SEC)
@@ -115,6 +131,14 @@ static void pty_forward_disconnect(PTYForward *f) {
}
f->saved_stdout = f->saved_stdin = false;
f->out_buffer = mfree(f->out_buffer);
f->out_buffer_size = 0;
f->out_buffer_full = 0;
f->in_buffer_full = 0;
f->csi_sequence = mfree(f->csi_sequence);
f->ansi_color_state = _ANSI_COLOR_STATE_INVALID;
}
static int pty_forward_done(PTYForward *f, int rcode) {
@@ -202,10 +226,243 @@ static bool drained(PTYForward *f) {
return true;
}
static int shovel(PTYForward *f) {
ssize_t k;
static char *background_color_sequence(PTYForward *f) {
assert(f);
assert(f->background_color);
/* This sets the background color to the desired one, and erase the rest of the line with it */
return strjoin("\x1B[", f->background_color, "m", ANSI_ERASE_TO_END_OF_LINE);
}
static int insert_string(PTYForward *f, size_t offset, const char *s) {
assert(f);
assert(offset <= f->out_buffer_full);
assert(s);
size_t l = strlen(s);
assert(l <= INT_MAX); /* Make sure we can still return this */
void *p = realloc(f->out_buffer, MAX(f->out_buffer_full + l, (size_t) LINE_MAX));
if (!p)
return -ENOMEM;
f->out_buffer = p;
f->out_buffer_size = MALLOC_SIZEOF_SAFE(f->out_buffer);
memmove(f->out_buffer + offset + l, f->out_buffer + offset, f->out_buffer_full - offset);
memcpy(f->out_buffer + offset, s, l);
f->out_buffer_full += l;
return (int) l;
}
static int insert_erase_newline(PTYForward *f, size_t offset) {
_cleanup_free_ char *s = NULL;
assert(f);
assert(f->background_color);
s = background_color_sequence(f);
if (!s)
return -ENOMEM;
return insert_string(f, offset, s);
}
static int is_csi_background_reset_sequence(const char *seq) {
enum {
COLOR_TOKEN_NO,
COLOR_TOKEN_START,
COLOR_TOKEN_8BIT,
COLOR_TOKEN_24BIT_R,
COLOR_TOKEN_24BIT_G,
COLOR_TOKEN_24BIT_B,
} token_state = COLOR_TOKEN_NO;
bool b = false;
int r;
assert(seq);
/* This parses CSI "m" sequences, and determines if they reset the background color. If so returns
* 1. This can then be used to insert another sequence that sets the color to the desired
* replacement. */
for (;;) {
_cleanup_free_ char *token = NULL;
r = extract_first_word(&seq, &token, ";", EXTRACT_RELAX|EXTRACT_DONT_COALESCE_SEPARATORS|EXTRACT_RETAIN_ESCAPE);
if (r < 0)
return r;
if (r == 0)
break;
switch (token_state) {
case COLOR_TOKEN_NO:
if (STR_IN_SET(token, "", "0", "00", "49"))
b = true; /* These tokens set the background back to normal */
else if (STR_IN_SET(token, "40", "41", "42", "43", "44", "45", "46", "47", "48"))
b = false; /* And these tokens set them to something other than normal */
if (STR_IN_SET(token, "38", "48", "58"))
token_state = COLOR_TOKEN_START; /* These tokens mean an 8bit or 24bit color will follow */
break;
case COLOR_TOKEN_START:
if (STR_IN_SET(token, "5", "05"))
token_state = COLOR_TOKEN_8BIT; /* 8bit color */
else if (STR_IN_SET(token, "2", "02"))
token_state = COLOR_TOKEN_24BIT_R; /* 24bit color */
else
token_state = COLOR_TOKEN_NO; /* something weird? */
break;
case COLOR_TOKEN_24BIT_R:
token_state = COLOR_TOKEN_24BIT_G;
break;
case COLOR_TOKEN_24BIT_G:
token_state = COLOR_TOKEN_24BIT_B;
break;
case COLOR_TOKEN_8BIT:
case COLOR_TOKEN_24BIT_B:
token_state = COLOR_TOKEN_NO;
break;
}
}
return b;
}
static int insert_background_fix(PTYForward *f, size_t offset) {
assert(f);
assert(f->background_color);
if (!is_csi_background_reset_sequence(strempty(f->csi_sequence)))
return 0;
_cleanup_free_ char *s = NULL;
s = strjoin(";", f->background_color);
if (!s)
return -ENOMEM;
return insert_string(f, offset, s);
}
static int pty_forward_ansi_process(PTYForward *f, size_t offset) {
int r;
assert(f);
assert(offset <= f->out_buffer_full);
if (!f->background_color)
return 0;
for (size_t i = offset; i < f->out_buffer_full; i++) {
char c = f->out_buffer[i];
switch (f->ansi_color_state) {
case ANSI_COLOR_STATE_TEXT:
if (c == '\n')
f->ansi_color_state = ANSI_COLOR_STATE_NEWLINE;
if (c == 0x1B) /* ESC */
f->ansi_color_state = ANSI_COLOR_STATE_ESC;
break;
case ANSI_COLOR_STATE_NEWLINE: {
/* Immediately after a newline insert an ANSI sequence to erase the line with a background color */
r = insert_erase_newline(f, i);
if (r < 0)
return r;
i += r;
f->ansi_color_state = ANSI_COLOR_STATE_TEXT;
break;
}
case ANSI_COLOR_STATE_ESC: {
if (c == '[')
f->ansi_color_state = ANSI_COLOR_STATE_CSI_SEQUENCE;
else
f->ansi_color_state = ANSI_COLOR_STATE_TEXT;
break;
}
case ANSI_COLOR_STATE_CSI_SEQUENCE: {
if (c >= 0x20 && c <= 0x3F) {
/* If this is a "parameter" or "intermediary" byte (i.e. ranges 0x20…0x2F and
* 0x30…0x3F) then we are still in the CSI sequence */
if (strlen_ptr(f->csi_sequence) >= 64) {
/* Safety check: lets not accept unbounded CSI sequences */
f->csi_sequence = mfree(f->csi_sequence);
f->ansi_color_state = ANSI_COLOR_STATE_TEXT;
} else if (!strextend(&f->csi_sequence, CHAR_TO_STR(c)))
return -ENOMEM;
} else {
/* Otherwise, the CSI sequence is over */
if (c == 'm') {
/* This is an "SGR" (Select Graphic Rendition) sequence. Patch in our background color. */
r = insert_background_fix(f, i);
if (r < 0)
return r;
i += r;
}
f->csi_sequence = mfree(f->csi_sequence);
f->ansi_color_state = ANSI_COLOR_STATE_TEXT;
}
break;
}
default:
assert_not_reached();
}
}
return 0;
}
static int shovel(PTYForward *f) {
ssize_t k;
int r;
assert(f);
if (f->out_buffer_size == 0 && f->background_color) {
/* Erase the first line when we start */
f->out_buffer = background_color_sequence(f);
if (!f->out_buffer)
return pty_forward_done(f, log_oom());
f->out_buffer_full = strlen(f->out_buffer);
f->out_buffer_size = MALLOC_SIZEOF_SAFE(f->out_buffer);
}
if (f->out_buffer_size < LINE_MAX) {
/* Make sure we always have room for at least one "line" */
void *p = realloc(f->out_buffer, LINE_MAX);
if (!p)
return pty_forward_done(f, log_oom());
f->out_buffer = p;
f->out_buffer_size = MALLOC_SIZEOF_SAFE(p);
}
while ((f->stdin_readable && f->in_buffer_full <= 0) ||
(f->master_writable && f->in_buffer_full > 0) ||
@@ -267,9 +524,9 @@ static int shovel(PTYForward *f) {
}
}
if (f->master_readable && f->out_buffer_full < LINE_MAX) {
if (f->master_readable && f->out_buffer_full < MIN(f->out_buffer_size, (size_t) LINE_MAX)) {
k = read(f->master, f->out_buffer + f->out_buffer_full, LINE_MAX - f->out_buffer_full);
k = read(f->master, f->out_buffer + f->out_buffer_full, f->out_buffer_size - f->out_buffer_full);
if (k < 0) {
/* Note that EIO on the master device might be caused by vhangup() or
@@ -289,7 +546,12 @@ static int shovel(PTYForward *f) {
}
} else {
f->read_from_master = true;
size_t scan_index = f->out_buffer_full;
f->out_buffer_full += (size_t) k;
r = pty_forward_ansi_process(f, scan_index);
if (r < 0)
return pty_forward_done(f, log_error_errno(r, "Failed to scan for ANSI sequences: %m"));
}
}
@@ -555,6 +817,7 @@ PTYForward *pty_forward_free(PTYForward *f) {
if (!f)
return NULL;
pty_forward_disconnect(f);
free(f->background_color);
return mfree(f);
}
@@ -689,3 +952,9 @@ int pty_forward_set_width_height(PTYForward *f, unsigned width, unsigned height)
return 0;
}
int pty_forward_set_background_color(PTYForward *f, const char *color) {
assert(f);
return free_and_strdup(&f->background_color, color);
}
+2
View File
@@ -39,4 +39,6 @@ int pty_forward_set_priority(PTYForward *f, int64_t priority);
int pty_forward_set_width_height(PTYForward *f, unsigned width, unsigned height);
int pty_forward_set_background_color(PTYForward *f, const char *color);
DEFINE_TRIVIAL_CLEANUP_FUNC(PTYForward*, pty_forward_free);
+1
View File
@@ -57,6 +57,7 @@ simple_tests += files(
'test-cgroup.c',
'test-chase.c',
'test-clock.c',
'test-color-util.c',
'test-compare-operator.c',
'test-condition.c',
'test-conf-files.c',
+63
View File
@@ -0,0 +1,63 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "color-util.h"
#include "tests.h"
TEST(hsv_to_rgb) {
uint8_t r, g, b;
hsv_to_rgb(0, 0, 0, &r, &g, &b);
assert(r == 0 && g == 0 && b == 0);
hsv_to_rgb(60, 0, 0, &r, &g, &b);
assert(r == 0 && g == 0 && b == 0);
hsv_to_rgb(0, 0, 100, &r, &g, &b);
assert(r == 255 && g == 255 && b == 255);
hsv_to_rgb(0, 100, 100, &r, &g, &b);
assert(r == 255 && g == 0 && b == 0);
hsv_to_rgb(120, 100, 100, &r, &g, &b);
assert(r == 0 && g == 255 && b == 0);
hsv_to_rgb(240, 100, 100, &r, &g, &b);
assert(r == 0 && g == 0 && b == 255);
hsv_to_rgb(311, 52, 62, &r, &g, &b);
assert(r == 158 && g == 75 && b == 143);
}
TEST(rgb_to_hsv) {
double h, s, v;
rgb_to_hsv(0, 0, 0, &h, &s, &v);
assert(s <= 0);
assert(v <= 0);
rgb_to_hsv(1, 1, 1, &h, &s, &v);
assert(s <= 0);
assert(v >= 100);
rgb_to_hsv(1, 0, 0, &h, &s, &v);
assert(h >= 359 || h <= 1);
assert(s >= 100);
assert(v >= 100);
rgb_to_hsv(0, 1, 0, &h, &s, &v);
assert(h >= 119 && h <= 121);
assert(s >= 100);
assert(v >= 100);
rgb_to_hsv(0, 0, 1, &h, &s, &v);
assert(h >= 239 && h <= 241);
assert(s >= 100);
assert(v >= 100);
rgb_to_hsv(0.5, 0.6, 0.7, &h, &s, &v);
assert(h >= 209 && h <= 211);
assert(s >= 28 && s <= 31);
assert(v >= 69 && v <= 71);
}
DEFINE_TEST_MAIN(LOG_DEBUG);
+21
View File
@@ -133,6 +133,27 @@ TEST(strv_env_assign) {
assert_se(streq(a[0], "a=A"));
}
TEST(strv_env_assignf) {
_cleanup_strv_free_ char **a = NULL;
assert_se(strv_env_assignf(&a, "a", "a") > 0);
assert_se(strv_env_assignf(&a, "a", "%c", 'a') == 0);
assert_se(strv_env_assignf(&a, "c", "xxx%iyyy", 5) > 0);
assert_se(strv_length(a) == 2);
assert_se(strv_equal(a, STRV_MAKE("a=a", "c=xxx5yyy")));
assert_se(strv_env_assignf(&a, "c", NULL) == 0);
assert_se(strv_env_assignf(&a, "b", "b") > 0);
assert_se(strv_env_assignf(&a, "a", "A") == 0);
assert_se(strv_env_assignf(&a, "b", NULL) == 0);
assert_se(strv_env_assignf(&a, "a=", "B") == -EINVAL);
assert_se(strv_length(a) == 1);
assert_se(streq(a[0], "a=A"));
}
TEST(strv_env_assign_many) {
_cleanup_strv_free_ char **a = NULL;
+11
View File
@@ -165,4 +165,15 @@ TEST(get_ctty) {
log_notice("Not invoked with stdin == ctty, cutting get_ctty() test short");
}
TEST(get_default_background_color) {
double red, green, blue;
int r;
r = get_default_background_color(&red, &green, &blue);
if (r < 0)
log_notice_errno(r, "Can't get terminal default background color: %m");
else
log_notice("R=%g G=%g B=%g", red, green, blue);
}
DEFINE_TEST_MAIN(LOG_INFO);
+4
View File
@@ -227,5 +227,9 @@ B=$(systemd-run -q --wait --pipe -p ProtectProc=invisible cat /proc/version)
assert_eq "$A" "$B"
# Check that invoking the tool under the uid0 alias name works
uid0 ls /
echo "$(uid0 echo foo)" = "foo"
umount /proc/version
rm "$V"