Compare commits

..

24 Commits

Author SHA1 Message Date
Lennart Poettering 60daebdec2
Merge pull request #15882 from poettering/pam-sudo-fixes-part4
More pam_systemd fixes split out of #15742
2020-05-28 00:27:51 +02:00
Lennart Poettering 38344f1a79
Merge pull request #15893 from DaanDeMeyer/firstboot-overwrite
systemd-firstboot: Add --force, --delete-root-password and --root-password-is-hashed options
2020-05-27 22:51:02 +02:00
Lennart Poettering e0d70f7691 pam_systemd: set legacy D-Bus path only if the runtime directory is validated 2020-05-27 22:47:30 +02:00
Lennart Poettering 6d06dfad85 pam_systemd: be more thorough when validating runtime paths 2020-05-27 22:47:15 +02:00
Daan De Meyer a5925354bb firstboot: Add --kernel-command-line option 2020-05-27 18:54:26 +02:00
Daan De Meyer 676339a191 firstboot: Add --root-password-hashed option 2020-05-27 18:54:26 +02:00
Daan De Meyer 97a1a1103c Remove systemd-firstboot --force entry from TODO 2020-05-27 18:54:26 +02:00
Daan De Meyer 4926ceaff3 firstboot: Add --delete-root-password option 2020-05-27 18:54:25 +02:00
Daan De Meyer b4909a3fd0 firstboot: Add --force option 2020-05-27 18:54:25 +02:00
Daan De Meyer 2da3dc69e7 fileio: Rename rename_and_apply_smack to rename_and_apply_smack_floor_label. 2020-05-27 18:54:25 +02:00
Daan De Meyer 90c81688ff fileio: Refactor sync_rights to take fds as arguments 2020-05-27 18:54:25 +02:00
Daan De Meyer 494735f3d0 sysusers: Move sync_rights and rename_and_apply_smack to basic 2020-05-27 18:54:25 +02:00
Daan De Meyer 1fbc95d388 firstboot: Don't check twice if /etc/shadow exists 2020-05-27 18:54:25 +02:00
Lennart Poettering bb2294e454
Merge pull request #15669 from andir/systemd-ipv6-pd-subnet-id
networkd: subnet id support for ipv6 prefix delegation
2020-05-27 18:47:26 +02:00
Lennart Poettering 6bce17455e
Merge pull request #15226 from benzea/benzea/xdg-autostart-generator
xdg-autostart-generator: a generator for XDG autostart files
2020-05-27 18:41:01 +02:00
Michael Biebl f978844eb6 man: fix conditional in homed.conf.xml 2020-05-27 16:59:42 +02:00
Benjamin Berg 2ad7597e44 fuzz: Add an XDG desktop file fuzzer
To test the XDG parser used by the xdg-autostart-generator.

Co-authored-by: Evgeny Vereshchagin <evvers@ya.ru>
2020-05-27 09:02:10 +02:00
Benjamin Berg 98e07533a2 test: Add test for XDG desktop file parsing and interpretation 2020-05-27 09:02:10 +02:00
Benjamin Berg 4540e698e8 man: Add systemd-xdg-autostart-generator man page 2020-05-27 09:02:10 +02:00
Benjamin Berg 8feca2472c xdg-autostart-generator: Add a generator for XDG autostart files
This generator can be used by desktop environments to launch autostart
applications and services. The feature is an opt-in, triggered by
xdg-desktop-autostart.target being activated.

Also included is the new binary xdg-autostart-condition. This binary is
used as an ExecCondition to test the OnlyShowIn and NotShowIn XDG
desktop file keys. These need to be evaluated against the
XDG_CURRENT_DESKTOP environment variable which may not be known at
generation time.

Co-authored-by: Henri Chain <henri.chain@enioka.com>
2020-05-27 09:02:10 +02:00
Benjamin Berg 8746820b87 sysv-generator: Downgrade directory listing fails to warning
This is not a fatal error and should therefore be a warning instead.
2020-05-27 08:59:59 +02:00
Andreas Rammhold 02e9e34bd9
networkd: Add support for setting a preferred subnet id for IPv6 PD leases
This allows users to configure a subnet id that should be used instead
of automatically (sequentially) assigned subnets. The previous attempt
had the downside that the subnet id would not be the same between
networkd restarts. In some setups it is desirable to have predictable
subnet ids across restarts of services and systems.

The code for the assignment had to be broken up into two pieces. One of
them is the old (sequential) assignment of prefixes and the other is the
new assignment based on configured subnet ids. The new assignment code
has to be executed first and has to be taken into account when (later
on) allocating the "old" subnets from the same pool.

Instead of having one iteration through the links we are now trying to
allocate a prefix for every link on every delegated prefix, unless they
received an assignment in a previous iteration.
2020-05-26 12:41:22 +02:00
Andreas Rammhold 171f625b9e
in-addr-util: removed in_addr_prefix_next implementation
The in_addr_prefix_nth function does everything this function did and
more. We can substitute 100% of its users with the new function.
2020-05-26 12:41:22 +02:00
Andreas Rammhold 863b99cdd9
in-addr-util: introduce in_addr_prefix_nth 2020-05-26 12:35:49 +02:00
39 changed files with 1834 additions and 215 deletions

2
TODO
View File

@ -256,8 +256,6 @@ Features:
* systemd-firstboot: teach it dissector magic, so that you can point it to some * systemd-firstboot: teach it dissector magic, so that you can point it to some
disk image and it will just set everything in it all behind the scenes. disk image and it will just set everything in it all behind the scenes.
* systemd-firstboot: add --force mode that replaces existing configuration.
* We should probably replace /var/log/README, /etc/rc.d/README with symlinks * We should probably replace /var/log/README, /etc/rc.d/README with symlinks
that are linked to these places instead of copied. After all they are that are linked to these places instead of copied. After all they are
constant vendor data. constant vendor data.

View File

@ -3,7 +3,7 @@
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
<!-- SPDX-License-Identifier: LGPL-2.1+ --> <!-- SPDX-License-Identifier: LGPL-2.1+ -->
<refentry id="homed.conf" conditional='ENABLE_RESOLVE' <refentry id="homed.conf" conditional='ENABLE_HOMED'
xmlns:xi="http://www.w3.org/2001/XInclude"> xmlns:xi="http://www.w3.org/2001/XInclude">
<refentryinfo> <refentryinfo>
<title>homed.conf</title> <title>homed.conf</title>

View File

@ -975,6 +975,7 @@ manpages = [
['systemd-veritysetup'], ['systemd-veritysetup'],
'HAVE_LIBCRYPTSETUP'], 'HAVE_LIBCRYPTSETUP'],
['systemd-volatile-root.service', '8', ['systemd-volatile-root'], ''], ['systemd-volatile-root.service', '8', ['systemd-volatile-root'], ''],
['systemd-xdg-autostart-generator', '8', [], 'ENABLE_XDG_AUTOSTART'],
['systemd', '1', ['init'], ''], ['systemd', '1', ['init'], ''],
['systemd.automount', '5', [], ''], ['systemd.automount', '5', [], ''],
['systemd.device', '5', [], ''], ['systemd.device', '5', [], ''],

View File

@ -150,18 +150,27 @@
<varlistentry> <varlistentry>
<term><option>--root-password=<replaceable>PASSWORD</replaceable></option></term> <term><option>--root-password=<replaceable>PASSWORD</replaceable></option></term>
<term><option>--root-password-file=<replaceable>PATH</replaceable></option></term> <term><option>--root-password-file=<replaceable>PATH</replaceable></option></term>
<term><option>--root-password-hashed=<replaceable>HASHED_PASSWORD</replaceable></option></term>
<listitem><para>Sets the password of the system's root user. <listitem><para>Sets the password of the system's root user. This creates a
This creates a
<citerefentry project='die-net'><refentrytitle>shadow</refentrytitle><manvolnum>5</manvolnum></citerefentry> <citerefentry project='die-net'><refentrytitle>shadow</refentrytitle><manvolnum>5</manvolnum></citerefentry>
file. This setting exists in two forms: file. This setting exists in three forms: <option>--root-password=</option> accepts the password to
<option>--root-password=</option> accepts the password to set set directly on the command line, <option>--root-password-file=</option> reads it from a file and
directly on the command line, and <option>--root-password-hashed=</option> accepts an already hashed password on the command line. See
<option>--root-password-file=</option> reads it from a file. <citerefentry project='die-net'><refentrytitle>shadow</refentrytitle><manvolnum>5</manvolnum></citerefentry>
Note that it is not recommended to specify passwords on the for more information on the format of the hashed password. Note that it is not recommended to specify
command line, as other users might be able to see them simply plaintext passwords on the command line, as other users might be able to see them simply by invoking
by invoking <citerefentry project='die-net'><refentrytitle>ps</refentrytitle><manvolnum>1</manvolnum></citerefentry>.
<citerefentry project='die-net'><refentrytitle>ps</refentrytitle><manvolnum>1</manvolnum></citerefentry>.</para></listitem> </para></listitem>
</varlistentry>
<varlistentry>
<term><option>--kernel-command-line=<replaceable>CMDLINE</replaceable></option></term>
<listitem><para>Sets the system's kernel command line. This controls the
<filename>/etc/kernel/cmdline</filename> file which is used by
<citerefentry><refentrytitle>kernel-install</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
</para></listitem>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
@ -221,6 +230,23 @@
<option>--root=</option>.</para></listitem> <option>--root=</option>.</para></listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><option>--force</option></term>
<listitem><para>systemd-firstboot doesn't modify existing files unless <option>--force</option>
is specified. For modifications to <filename>/etc/passwd</filename> and
<filename>/etc/shadow</filename>, systemd-firstboot only modifies the entry of the
<literal>root</literal> user instead of overwriting the entire file.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--delete-root-password</option></term>
<listitem><para>Removes the password of the system's root user, enabling login as root without a
password unless the root account is locked. Note that this is extremely insecure and hence this
option should not be used lightly.</para></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="help" /> <xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" /> <xi:include href="standard-options.xml" xpointer="version" />
</variablelist> </variablelist>

View File

@ -0,0 +1,57 @@
<?xml version="1.0"?>
<!--*-nxml-*-->
<!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+ -->
<refentry id="systemd-xdg-autostart-generator" conditional="ENABLE_XDG_AUTOSTART">
<refentryinfo>
<title>systemd-xdg-autostart-generator</title>
<productname>systemd</productname>
</refentryinfo>
<refmeta>
<refentrytitle>systemd-xdg-autostart-generator</refentrytitle>
<manvolnum>8</manvolnum>
</refmeta>
<refnamediv>
<refname>systemd-xdg-autostart-generator</refname>
<refpurpose>User unit generator for XDG autostart files</refpurpose>
</refnamediv>
<refsynopsisdiv>
<para><filename>/usr/lib/systemd/system-generators/systemd-xdg-autostart-generator</filename></para>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para><filename>systemd-xdg-autostart-generator</filename> is a generator
that creates .service units for
<ulink url="https://specifications.freedesktop.org/autostart-spec/autostart-spec-latest.html">XDG autostart</ulink>
files.
This permits desktop environments to delegate startup of these applications to
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>
.</para>
<para>Units created by <filename>systemd-xdg-autostart-generator</filename>
can be started by the desktop environment using <literal>xdg-desktop-autostart.target</literal>.
See
<citerefentry><refentrytitle>systemd.special</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for more details.</para>
<para><filename>systemd-xdg-autostart-generator</filename> implements
<citerefentry><refentrytitle>systemd.generator</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para>
</refsect1>
<refsect1>
<title>See Also</title>
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd.target</refentrytitle><manvolnum>5</manvolnum></citerefentry>
</para>
</refsect1>
</refentry>

View File

@ -310,6 +310,7 @@ find $dir</programlisting>
<citerefentry><refentrytitle>systemd-rc-local-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>, <citerefentry><refentrytitle>systemd-rc-local-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-system-update-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>, <citerefentry><refentrytitle>systemd-system-update-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-sysv-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>, <citerefentry><refentrytitle>systemd-sysv-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-xdg-autostart-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>, <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>, <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd.environment-generator</refentrytitle><manvolnum>7</manvolnum></citerefentry> <citerefentry><refentrytitle>systemd.environment-generator</refentrytitle><manvolnum>7</manvolnum></citerefentry>

View File

@ -720,6 +720,16 @@
sections for more configuration options. sections for more configuration options.
</para></listitem> </para></listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><varname>IPv6PDSubnetId=</varname></term>
<listitem><para>Configure a specific subnet ID on the interface from a (previously) received prefix delegation.
You can either set "auto" (the default) or a specific subnet ID
(as defined in <ulink url="https://tools.ietf.org/html/rfc4291#section-2.5.4">RFC 4291</ulink>, section 2.5.4),
in which case the allowed value is hexadecimal, from 0 to 0x7fffffffffffffff inclusive.
This option is only effective when used together with <varname>IPv6PrefixDelegation=</varname>
and the corresponding configuration on the upstream interface.
</para></listitem>
</varlistentry>
<varlistentry> <varlistentry>
<term><varname>IPv6MTUBytes=</varname></term> <term><varname>IPv6MTUBytes=</varname></term>
<listitem><para>Configures IPv6 maximum transmission unit (MTU). <listitem><para>Configures IPv6 maximum transmission unit (MTU).

View File

@ -1150,6 +1150,18 @@
<filename>gnome-session.target</filename>.</para> <filename>gnome-session.target</filename>.</para>
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><filename>xdg-desktop-autostart.target</filename></term>
<listitem>
<para>The XDG specification defines a way to autostart applications using XDG desktop files.
systemd ships
<citerefentry><refentrytitle>systemd-xdg-autostart-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>
for the XDG desktop files in autostart directories.
Desktop Environments can opt-in to use this service by adding a <varname>Wants=</varname>
dependency on <literal>xdg-desktop-autostart.target</literal></para>.
</listitem>
</varlistentry>
</variablelist> </variablelist>
</refsect2> </refsect2>
</refsect1> </refsect1>

View File

@ -1417,6 +1417,7 @@ foreach term : ['utmp',
'tmpfiles', 'tmpfiles',
'hwdb', 'hwdb',
'rfkill', 'rfkill',
'xdg-autostart',
'ldconfig', 'ldconfig',
'efi', 'efi',
'tpm', 'tpm',
@ -1520,6 +1521,7 @@ includes = include_directories('src/basic',
'src/libudev', 'src/libudev',
'src/core', 'src/core',
'src/shutdown', 'src/shutdown',
'src/xdg-autostart-generator',
'src/libsystemd/sd-bus', 'src/libsystemd/sd-bus',
'src/libsystemd/sd-device', 'src/libsystemd/sd-device',
'src/libsystemd/sd-event', 'src/libsystemd/sd-event',
@ -2301,6 +2303,27 @@ if conf.get('HAVE_SYSV_COMPAT') == 1
install_dir : systemgeneratordir) install_dir : systemgeneratordir)
endif endif
if conf.get('ENABLE_XDG_AUTOSTART') == 1
executable(
'systemd-xdg-autostart-generator',
'src/xdg-autostart-generator/xdg-autostart-generator.c',
'src/xdg-autostart-generator/xdg-autostart-service.c',
include_directories : includes,
link_with : [libshared],
install_rpath : rootlibexecdir,
install : true,
install_dir : usergeneratordir)
executable(
'systemd-xdg-autostart-condition',
'src/xdg-autostart-generator/xdg-autostart-condition.c',
include_directories : includes,
link_with : [libshared],
install_rpath : rootlibexecdir,
install : true,
install_dir : rootlibexecdir)
endif
if conf.get('ENABLE_HOSTNAMED') == 1 if conf.get('ENABLE_HOSTNAMED') == 1
executable( executable(
'systemd-hostnamed', 'systemd-hostnamed',
@ -3566,6 +3589,7 @@ foreach tuple : [
['randomseed'], ['randomseed'],
['backlight'], ['backlight'],
['rfkill'], ['rfkill'],
['xdg-autostart'],
['logind'], ['logind'],
['machined'], ['machined'],
['portabled'], ['portabled'],

View File

@ -142,6 +142,8 @@ option('hwdb', type : 'boolean',
description : 'support for the hardware database') description : 'support for the hardware database')
option('rfkill', type : 'boolean', option('rfkill', type : 'boolean',
description : 'support for the rfkill tools') description : 'support for the rfkill tools')
option('xdg-autostart', type : 'boolean',
description : 'install the xdg-autostart-generator and unit')
option('man', type : 'combo', choices : ['auto', 'true', 'false'], option('man', type : 'combo', choices : ['auto', 'true', 'false'],
value : 'false', value : 'false',
description : 'build and install man pages') description : 'build and install man pages')

View File

@ -1187,3 +1187,25 @@ int warn_file_is_world_accessible(const char *filename, struct stat *st, const c
filename, st->st_mode & 07777); filename, st->st_mode & 07777);
return 0; return 0;
} }
int sync_rights(int from, int to) {
struct stat st;
if (fstat(from, &st) < 0)
return -errno;
return fchmod_and_chown(to, st.st_mode & 07777, st.st_uid, st.st_gid);
}
int rename_and_apply_smack_floor_label(const char *from, const char *to) {
int r = 0;
if (rename(from, to) < 0)
return -errno;
#ifdef SMACK_RUN_LABEL
r = mac_smack_apply(to, SMACK_ATTR_ACCESS, SMACK_FLOOR_LABEL);
if (r < 0)
return r;
#endif
return r;
}

View File

@ -106,3 +106,7 @@ static inline int read_nul_string(FILE *f, size_t limit, char **ret) {
int safe_fgetc(FILE *f, char *ret); int safe_fgetc(FILE *f, char *ret);
int warn_file_is_world_accessible(const char *filename, struct stat *st, const char *unit, unsigned line); int warn_file_is_world_accessible(const char *filename, struct stat *st, const char *unit, unsigned line);
int sync_rights(int from, int to);
int rename_and_apply_smack_floor_label(const char *temp_path, const char *dest_path);

View File

@ -177,47 +177,89 @@ int in_addr_prefix_next(int family, union in_addr_union *u, unsigned prefixlen)
assert(u); assert(u);
/* Increases the network part of an address by one. Returns /* Increases the network part of an address by one. Returns
* positive it that succeeds, or 0 if this overflows. */ * positive if that succeeds, or -ERANGE if this overflows. */
return in_addr_prefix_nth(family, u, prefixlen, 1);
}
/*
* Calculates the nth prefix of size prefixlen starting from the address denoted by u.
*
* On success 1 will be returned and the calculated prefix will be available in
* u. In the case nth == 0 the input will be left unchanged and 1 will be returned.
* In case the calculation cannot be performed (invalid prefix length,
* overflows would occur) -ERANGE is returned. If the address family given isn't
* supported -EAFNOSUPPORT will be returned.
*
*
* Examples:
* - in_addr_prefix_nth(AF_INET, 192.168.0.0, 24, 2), returns 1, writes 192.168.2.0 to u
* - in_addr_prefix_nth(AF_INET, 192.168.0.0, 24, 0), returns 1, no data written
* - in_addr_prefix_nth(AF_INET, 255.255.255.0, 24, 1), returns -ERANGE, no data written
* - in_addr_prefix_nth(AF_INET, 255.255.255.0, 0, 1), returns -ERANGE, no data written
* - in_addr_prefix_nth(AF_INET6, 2001:db8, 64, 0xff00) returns 1, writes 2001:0db8:0000:ff00:: to u
*/
int in_addr_prefix_nth(int family, union in_addr_union *u, unsigned prefixlen, uint64_t nth) {
assert(u);
if (prefixlen <= 0) if (prefixlen <= 0)
return 0; return -ERANGE;
if (nth == 0)
return 1;
if (family == AF_INET) { if (family == AF_INET) {
uint32_t c, n; uint32_t c, n, t;
if (prefixlen > 32) if (prefixlen > 32)
prefixlen = 32; prefixlen = 32;
c = be32toh(u->in.s_addr); c = be32toh(u->in.s_addr);
n = c + (1UL << (32 - prefixlen));
if (n < c)
return 0;
n &= 0xFFFFFFFFUL << (32 - prefixlen);
t = nth << (32 - prefixlen);
/* Check for wrap */
if (c > UINT32_MAX - t)
return -ERANGE;
n = c + t;
n &= UINT32_C(0xFFFFFFFF) << (32 - prefixlen);
u->in.s_addr = htobe32(n); u->in.s_addr = htobe32(n);
return 1; return 1;
} }
if (family == AF_INET6) { if (family == AF_INET6) {
struct in6_addr add = {}, result; struct in6_addr result = {};
uint8_t overflow = 0; uint8_t overflow = 0;
unsigned i; uint64_t delta; /* this assumes that we only ever have to up to 1<<64 subnets */
unsigned start_byte = (prefixlen - 1) / 8;
if (prefixlen > 128) if (prefixlen > 128)
prefixlen = 128; prefixlen = 128;
/* First calculate what we have to add */ /* First calculate what we have to add */
add.s6_addr[(prefixlen-1) / 8] = 1 << (7 - (prefixlen-1) % 8); delta = nth << ((128 - prefixlen) % 8);
for (i = 16; i > 0; i--) { for (unsigned i = 16; i > 0; i--) {
unsigned j = i - 1; unsigned j = i - 1;
unsigned d = 0;
result.s6_addr[j] = u->in6.s6_addr[j] + add.s6_addr[j] + overflow; if (j <= start_byte) {
overflow = (result.s6_addr[j] < u->in6.s6_addr[j]); int16_t t;
d = delta & 0xFF;
delta >>= 8;
t = u->in6.s6_addr[j] + d + overflow;
overflow = t > UINT8_MAX ? t - UINT8_MAX : 0;
result.s6_addr[j] = (uint8_t)t;
} else
result.s6_addr[j] = u->in6.s6_addr[j];
} }
if (overflow) if (overflow || delta != 0)
return 0; return -ERANGE;
u->in6 = result; u->in6 = result;
return 1; return 1;

View File

@ -36,6 +36,7 @@ bool in4_addr_equal(const struct in_addr *a, const struct in_addr *b);
int in_addr_equal(int family, const union in_addr_union *a, const union in_addr_union *b); int in_addr_equal(int family, const union in_addr_union *a, const union in_addr_union *b);
int in_addr_prefix_intersect(int family, const union in_addr_union *a, unsigned aprefixlen, const union in_addr_union *b, unsigned bprefixlen); int in_addr_prefix_intersect(int family, const union in_addr_union *a, unsigned aprefixlen, const union in_addr_union *b, unsigned bprefixlen);
int in_addr_prefix_next(int family, union in_addr_union *u, unsigned prefixlen); int in_addr_prefix_next(int family, union in_addr_union *u, unsigned prefixlen);
int in_addr_prefix_nth(int family, union in_addr_union *u, unsigned prefixlen, uint64_t nth);
int in_addr_random_prefix(int family, union in_addr_union *u, unsigned prefixlen_fixed_part, unsigned prefixlen); int in_addr_random_prefix(int family, union in_addr_union *u, unsigned prefixlen_fixed_part, unsigned prefixlen);
int in_addr_to_string(int family, const union in_addr_union *u, char **ret); int in_addr_to_string(int family, const union in_addr_union *u, char **ret);
int in_addr_prefix_to_string(int family, const union in_addr_union *u, unsigned prefixlen, char **ret); int in_addr_prefix_to_string(int family, const union in_addr_union *u, unsigned prefixlen, char **ret);

View File

@ -30,6 +30,7 @@
#include "strv.h" #include "strv.h"
#include "terminal-util.h" #include "terminal-util.h"
#include "time-util.h" #include "time-util.h"
#include "tmpfile-util-label.h"
#include "umask-util.h" #include "umask-util.h"
#include "user-util.h" #include "user-util.h"
@ -41,6 +42,7 @@ static char *arg_timezone = NULL;
static char *arg_hostname = NULL; static char *arg_hostname = NULL;
static sd_id128_t arg_machine_id = {}; static sd_id128_t arg_machine_id = {};
static char *arg_root_password = NULL; static char *arg_root_password = NULL;
static char *arg_kernel_cmdline = NULL;
static bool arg_prompt_locale = false; static bool arg_prompt_locale = false;
static bool arg_prompt_keymap = false; static bool arg_prompt_keymap = false;
static bool arg_prompt_timezone = false; static bool arg_prompt_timezone = false;
@ -50,6 +52,9 @@ static bool arg_copy_locale = false;
static bool arg_copy_keymap = false; static bool arg_copy_keymap = false;
static bool arg_copy_timezone = false; static bool arg_copy_timezone = false;
static bool arg_copy_root_password = false; static bool arg_copy_root_password = false;
static bool arg_force = false;
static bool arg_delete_root_password = false;
static bool arg_root_password_is_hashed = false;
STATIC_DESTRUCTOR_REGISTER(arg_root, freep); STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_locale, freep); STATIC_DESTRUCTOR_REGISTER(arg_locale, freep);
@ -273,7 +278,7 @@ static int process_locale(void) {
int r; int r;
etc_localeconf = prefix_roota(arg_root, "/etc/locale.conf"); etc_localeconf = prefix_roota(arg_root, "/etc/locale.conf");
if (laccess(etc_localeconf, F_OK) >= 0) if (laccess(etc_localeconf, F_OK) >= 0 && !arg_force)
return 0; return 0;
if (arg_copy_locale && arg_root) { if (arg_copy_locale && arg_root) {
@ -340,7 +345,7 @@ static int process_keymap(void) {
int r; int r;
etc_vconsoleconf = prefix_roota(arg_root, "/etc/vconsole.conf"); etc_vconsoleconf = prefix_roota(arg_root, "/etc/vconsole.conf");
if (laccess(etc_vconsoleconf, F_OK) >= 0) if (laccess(etc_vconsoleconf, F_OK) >= 0 && !arg_force)
return 0; return 0;
if (arg_copy_keymap && arg_root) { if (arg_copy_keymap && arg_root) {
@ -412,7 +417,7 @@ static int process_timezone(void) {
int r; int r;
etc_localtime = prefix_roota(arg_root, "/etc/localtime"); etc_localtime = prefix_roota(arg_root, "/etc/localtime");
if (laccess(etc_localtime, F_OK) >= 0) if (laccess(etc_localtime, F_OK) >= 0 && !arg_force)
return 0; return 0;
if (arg_copy_timezone && arg_root) { if (arg_copy_timezone && arg_root) {
@ -492,7 +497,7 @@ static int process_hostname(void) {
int r; int r;
etc_hostname = prefix_roota(arg_root, "/etc/hostname"); etc_hostname = prefix_roota(arg_root, "/etc/hostname");
if (laccess(etc_hostname, F_OK) >= 0) if (laccess(etc_hostname, F_OK) >= 0 && !arg_force)
return 0; return 0;
r = prompt_hostname(); r = prompt_hostname();
@ -503,7 +508,8 @@ static int process_hostname(void) {
return 0; return 0;
r = write_string_file(etc_hostname, arg_hostname, r = write_string_file(etc_hostname, arg_hostname,
WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_SYNC | WRITE_STRING_FILE_MKDIR_0755); WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_SYNC | WRITE_STRING_FILE_MKDIR_0755 |
(arg_force ? WRITE_STRING_FILE_ATOMIC : 0));
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to write %s: %m", etc_hostname); return log_error_errno(r, "Failed to write %s: %m", etc_hostname);
@ -517,14 +523,15 @@ static int process_machine_id(void) {
int r; int r;
etc_machine_id = prefix_roota(arg_root, "/etc/machine-id"); etc_machine_id = prefix_roota(arg_root, "/etc/machine-id");
if (laccess(etc_machine_id, F_OK) >= 0) if (laccess(etc_machine_id, F_OK) >= 0 && !arg_force)
return 0; return 0;
if (sd_id128_is_null(arg_machine_id)) if (sd_id128_is_null(arg_machine_id))
return 0; return 0;
r = write_string_file(etc_machine_id, sd_id128_to_string(arg_machine_id, id), r = write_string_file(etc_machine_id, sd_id128_to_string(arg_machine_id, id),
WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_SYNC | WRITE_STRING_FILE_MKDIR_0755); WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_SYNC | WRITE_STRING_FILE_MKDIR_0755 |
(arg_force ? WRITE_STRING_FILE_ATOMIC : 0));
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to write machine id: %m"); return log_error_errno(r, "Failed to write machine id: %m");
@ -533,7 +540,7 @@ static int process_machine_id(void) {
} }
static int prompt_root_password(void) { static int prompt_root_password(void) {
const char *msg1, *msg2, *etc_shadow; const char *msg1, *msg2;
int r; int r;
if (arg_root_password) if (arg_root_password)
@ -542,10 +549,6 @@ static int prompt_root_password(void) {
if (!arg_prompt_root_password) if (!arg_prompt_root_password)
return 0; return 0;
etc_shadow = prefix_roota(arg_root, "/etc/shadow");
if (laccess(etc_shadow, F_OK) >= 0)
return 0;
print_welcome(); print_welcome();
putchar('\n'); putchar('\n');
@ -586,29 +589,105 @@ static int prompt_root_password(void) {
return 0; return 0;
} }
static int write_root_shadow(const char *path, const struct spwd *p) { static int write_root_passwd(const char *passwd_path, const char *password) {
_cleanup_fclose_ FILE *f = NULL; _cleanup_fclose_ FILE *original = NULL, *passwd = NULL;
_cleanup_(unlink_and_freep) char *passwd_tmp = NULL;
int r; int r;
assert(path); r = fopen_temporary_label("/etc/passwd", passwd_path, &passwd, &passwd_tmp);
assert(p);
RUN_WITH_UMASK(0777)
f = fopen(path, "wex");
if (!f)
return -errno;
r = putspent_sane(p, f);
if (r < 0) if (r < 0)
return r; return r;
return fflush_sync_and_check(f); original = fopen(passwd_path, "re");
if (original) {
struct passwd *i;
r = sync_rights(fileno(original), fileno(passwd));
if (r < 0)
return r;
while ((r = fgetpwent_sane(original, &i)) > 0) {
if (streq(i->pw_name, "root"))
i->pw_passwd = (char *) password;
r = putpwent_sane(i, passwd);
if (r < 0)
return r;
}
if (r < 0)
return r;
} else {
struct passwd root = {
.pw_name = (char *) "root",
.pw_passwd = (char *) password,
.pw_uid = 0,
.pw_gid = 0,
.pw_gecos = (char *) "Super User",
.pw_dir = (char *) "/root",
.pw_shell = (char *) "/bin/sh",
};
if (errno != ENOENT)
return -errno;
r = fchmod(fileno(passwd), 0000);
if (r < 0)
return -errno;
r = putpwent_sane(&root, passwd);
if (r < 0)
return r;
}
r = fflush_sync_and_check(passwd);
if (r < 0)
return r;
r = rename_and_apply_smack_floor_label(passwd_tmp, passwd_path);
if (r < 0)
return r;
return 0;
} }
static int process_root_password(void) { static int write_root_shadow(const char *shadow_path, const char *hashed_password) {
_cleanup_fclose_ FILE *original = NULL, *shadow = NULL;
_cleanup_(unlink_and_freep) char *shadow_tmp = NULL;
int r;
struct spwd item = { r = fopen_temporary_label("/etc/shadow", shadow_path, &shadow, &shadow_tmp);
if (r < 0)
return r;
original = fopen(shadow_path, "re");
if (original) {
struct spwd *i;
r = sync_rights(fileno(original), fileno(shadow));
if (r < 0)
return r;
while ((r = fgetspent_sane(original, &i)) > 0) {
if (streq(i->sp_namp, "root")) {
i->sp_pwdp = (char *) hashed_password;
i->sp_lstchg = (long) (now(CLOCK_REALTIME) / USEC_PER_DAY);
}
r = putspent_sane(i, shadow);
if (r < 0)
return r;
}
if (r < 0)
return r;
} else {
struct spwd root = {
.sp_namp = (char*) "root", .sp_namp = (char*) "root",
.sp_pwdp = (char *) hashed_password,
.sp_lstchg = (long) (now(CLOCK_REALTIME) / USEC_PER_DAY),
.sp_min = -1, .sp_min = -1,
.sp_max = -1, .sp_max = -1,
.sp_warn = -1, .sp_warn = -1,
@ -616,15 +695,39 @@ static int process_root_password(void) {
.sp_expire = -1, .sp_expire = -1,
.sp_flag = (unsigned long) -1, /* this appears to be what everybody does ... */ .sp_flag = (unsigned long) -1, /* this appears to be what everybody does ... */
}; };
_cleanup_free_ char *salt = NULL;
if (errno != ENOENT)
return -errno;
r = fchmod(fileno(shadow), 0000);
if (r < 0)
return -errno;
r = putspent_sane(&root, shadow);
if (r < 0)
return r;
}
r = fflush_sync_and_check(shadow);
if (r < 0)
return r;
r = rename_and_apply_smack_floor_label(shadow_tmp, shadow_path);
if (r < 0)
return r;
return 0;
}
static int process_root_password(void) {
_cleanup_close_ int lock = -1; _cleanup_close_ int lock = -1;
struct crypt_data cd = {}; struct crypt_data cd = {};
const char *hashed_password;
const char *etc_shadow; const char *etc_shadow;
int r; int r;
etc_shadow = prefix_roota(arg_root, "/etc/shadow"); etc_shadow = prefix_roota(arg_root, "/etc/shadow");
if (laccess(etc_shadow, F_OK) >= 0) if (laccess(etc_shadow, F_OK) >= 0 && !arg_force)
return 0; return 0;
(void) mkdir_parents(etc_shadow, 0755); (void) mkdir_parents(etc_shadow, 0755);
@ -633,6 +736,21 @@ static int process_root_password(void) {
if (lock < 0) if (lock < 0)
return log_error_errno(lock, "Failed to take a lock: %m"); return log_error_errno(lock, "Failed to take a lock: %m");
if (arg_delete_root_password) {
const char *etc_passwd;
/* Mixing alloca() and other stuff that touches the stack in one expression is not portable. */
etc_passwd = prefix_roota(arg_root, "/etc/passwd");
r = write_root_passwd(etc_passwd, "");
if (r < 0)
return log_error_errno(r, "Failed to write %s: %m", etc_passwd);
log_info("%s written", etc_passwd);
return 0;
}
if (arg_copy_root_password && arg_root) { if (arg_copy_root_password && arg_root) {
struct spwd *p; struct spwd *p;
@ -646,7 +764,7 @@ static int process_root_password(void) {
return log_error_errno(errno, "Failed to find shadow entry for root: %m"); return log_error_errno(errno, "Failed to find shadow entry for root: %m");
} }
r = write_root_shadow(etc_shadow, p); r = write_root_shadow(etc_shadow, p->sp_pwdp);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to write %s: %m", etc_shadow); return log_error_errno(r, "Failed to write %s: %m", etc_shadow);
@ -662,19 +780,24 @@ static int process_root_password(void) {
if (!arg_root_password) if (!arg_root_password)
return 0; return 0;
if (arg_root_password_is_hashed)
hashed_password = arg_root_password;
else {
_cleanup_free_ char *salt = NULL;
/* hashed_password points inside cd after crypt_r returns so cd has function scope. */
r = make_salt(&salt); r = make_salt(&salt);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to get salt: %m"); return log_error_errno(r, "Failed to get salt: %m");
errno = 0; errno = 0;
item.sp_pwdp = crypt_r(arg_root_password, salt, &cd); hashed_password = crypt_r(arg_root_password, salt, &cd);
if (!item.sp_pwdp) if (!hashed_password)
return log_error_errno(errno == 0 ? SYNTHETIC_ERRNO(EINVAL) : errno, return log_error_errno(errno == 0 ? SYNTHETIC_ERRNO(EINVAL) : errno,
"Failed to encrypt password: %m"); "Failed to encrypt password: %m");
}
item.sp_lstchg = (long) (now(CLOCK_REALTIME) / USEC_PER_DAY); r = write_root_shadow(etc_shadow, hashed_password);
r = write_root_shadow(etc_shadow, &item);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to write %s: %m", etc_shadow); return log_error_errno(r, "Failed to write %s: %m", etc_shadow);
@ -682,6 +805,27 @@ static int process_root_password(void) {
return 0; return 0;
} }
static int process_kernel_cmdline(void) {
const char *etc_kernel_cmdline;
int r;
etc_kernel_cmdline = prefix_roota(arg_root, "/etc/kernel/cmdline");
if (laccess(etc_kernel_cmdline, F_OK) >= 0 && !arg_force)
return 0;
if (!arg_kernel_cmdline)
return 0;
r = write_string_file(etc_kernel_cmdline, arg_kernel_cmdline,
WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_SYNC | WRITE_STRING_FILE_MKDIR_0755 |
(arg_force ? WRITE_STRING_FILE_ATOMIC : 0));
if (r < 0)
return log_error_errno(r, "Failed to write %s: %m", etc_kernel_cmdline);
log_info("%s written.", etc_kernel_cmdline);
return 0;
}
static int help(void) { static int help(void) {
_cleanup_free_ char *link = NULL; _cleanup_free_ char *link = NULL;
int r; int r;
@ -701,8 +845,9 @@ static int help(void) {
" --timezone=TIMEZONE Set timezone\n" " --timezone=TIMEZONE Set timezone\n"
" --hostname=NAME Set hostname\n" " --hostname=NAME Set hostname\n"
" --machine-ID=ID Set machine ID\n" " --machine-ID=ID Set machine ID\n"
" --root-password=PASSWORD Set root password\n" " --root-password=PASSWORD Set root password from plaintext password\n"
" --root-password-file=FILE Set root password from file\n" " --root-password-file=FILE Set root password from file\n"
" --root-password-hashed=HASHED_PASSWORD Set root password from hashed password\n"
" --prompt-locale Prompt the user for locale settings\n" " --prompt-locale Prompt the user for locale settings\n"
" --prompt-keymap Prompt the user for keymap settings\n" " --prompt-keymap Prompt the user for keymap settings\n"
" --prompt-timezone Prompt the user for timezone\n" " --prompt-timezone Prompt the user for timezone\n"
@ -715,6 +860,8 @@ static int help(void) {
" --copy-root-password Copy root password from host\n" " --copy-root-password Copy root password from host\n"
" --copy Copy locale, keymap, timezone, root password\n" " --copy Copy locale, keymap, timezone, root password\n"
" --setup-machine-id Generate a new random machine ID\n" " --setup-machine-id Generate a new random machine ID\n"
" --force Overwrite existing files\n"
" --delete-root-password Delete root password\n"
"\nSee the %s for details.\n" "\nSee the %s for details.\n"
, program_invocation_short_name , program_invocation_short_name
, link , link
@ -736,6 +883,8 @@ static int parse_argv(int argc, char *argv[]) {
ARG_MACHINE_ID, ARG_MACHINE_ID,
ARG_ROOT_PASSWORD, ARG_ROOT_PASSWORD,
ARG_ROOT_PASSWORD_FILE, ARG_ROOT_PASSWORD_FILE,
ARG_ROOT_PASSWORD_HASHED,
ARG_KERNEL_COMMAND_LINE,
ARG_PROMPT, ARG_PROMPT,
ARG_PROMPT_LOCALE, ARG_PROMPT_LOCALE,
ARG_PROMPT_KEYMAP, ARG_PROMPT_KEYMAP,
@ -748,6 +897,8 @@ static int parse_argv(int argc, char *argv[]) {
ARG_COPY_TIMEZONE, ARG_COPY_TIMEZONE,
ARG_COPY_ROOT_PASSWORD, ARG_COPY_ROOT_PASSWORD,
ARG_SETUP_MACHINE_ID, ARG_SETUP_MACHINE_ID,
ARG_FORCE,
ARG_DELETE_ROOT_PASSWORD,
}; };
static const struct option options[] = { static const struct option options[] = {
@ -762,6 +913,8 @@ static int parse_argv(int argc, char *argv[]) {
{ "machine-id", required_argument, NULL, ARG_MACHINE_ID }, { "machine-id", required_argument, NULL, ARG_MACHINE_ID },
{ "root-password", required_argument, NULL, ARG_ROOT_PASSWORD }, { "root-password", required_argument, NULL, ARG_ROOT_PASSWORD },
{ "root-password-file", required_argument, NULL, ARG_ROOT_PASSWORD_FILE }, { "root-password-file", required_argument, NULL, ARG_ROOT_PASSWORD_FILE },
{ "root-password-hashed", required_argument, NULL, ARG_ROOT_PASSWORD_HASHED },
{ "kernel-command-line", required_argument, NULL, ARG_KERNEL_COMMAND_LINE },
{ "prompt", no_argument, NULL, ARG_PROMPT }, { "prompt", no_argument, NULL, ARG_PROMPT },
{ "prompt-locale", no_argument, NULL, ARG_PROMPT_LOCALE }, { "prompt-locale", no_argument, NULL, ARG_PROMPT_LOCALE },
{ "prompt-keymap", no_argument, NULL, ARG_PROMPT_KEYMAP }, { "prompt-keymap", no_argument, NULL, ARG_PROMPT_KEYMAP },
@ -774,6 +927,8 @@ static int parse_argv(int argc, char *argv[]) {
{ "copy-timezone", no_argument, NULL, ARG_COPY_TIMEZONE }, { "copy-timezone", no_argument, NULL, ARG_COPY_TIMEZONE },
{ "copy-root-password", no_argument, NULL, ARG_COPY_ROOT_PASSWORD }, { "copy-root-password", no_argument, NULL, ARG_COPY_ROOT_PASSWORD },
{ "setup-machine-id", no_argument, NULL, ARG_SETUP_MACHINE_ID }, { "setup-machine-id", no_argument, NULL, ARG_SETUP_MACHINE_ID },
{ "force", no_argument, NULL, ARG_FORCE },
{ "delete-root-password", no_argument, NULL, ARG_DELETE_ROOT_PASSWORD },
{} {}
}; };
@ -838,6 +993,8 @@ static int parse_argv(int argc, char *argv[]) {
r = free_and_strdup(&arg_root_password, optarg); r = free_and_strdup(&arg_root_password, optarg);
if (r < 0) if (r < 0)
return log_oom(); return log_oom();
arg_root_password_is_hashed = false;
break; break;
case ARG_ROOT_PASSWORD_FILE: case ARG_ROOT_PASSWORD_FILE:
@ -847,6 +1004,15 @@ static int parse_argv(int argc, char *argv[]) {
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to read %s: %m", optarg); return log_error_errno(r, "Failed to read %s: %m", optarg);
arg_root_password_is_hashed = false;
break;
case ARG_ROOT_PASSWORD_HASHED:
r = free_and_strdup(&arg_root_password, optarg);
if (r < 0)
return log_oom();
arg_root_password_is_hashed = true;
break; break;
case ARG_HOSTNAME: case ARG_HOSTNAME:
@ -868,6 +1034,13 @@ static int parse_argv(int argc, char *argv[]) {
break; break;
case ARG_KERNEL_COMMAND_LINE:
r = free_and_strdup(&arg_kernel_cmdline, optarg);
if (r < 0)
return log_oom();
break;
case ARG_PROMPT: case ARG_PROMPT:
arg_prompt_locale = arg_prompt_keymap = arg_prompt_timezone = arg_prompt_hostname = arg_prompt_root_password = true; arg_prompt_locale = arg_prompt_keymap = arg_prompt_timezone = arg_prompt_hostname = arg_prompt_root_password = true;
break; break;
@ -920,6 +1093,14 @@ static int parse_argv(int argc, char *argv[]) {
break; break;
case ARG_FORCE:
arg_force = true;
break;
case ARG_DELETE_ROOT_PASSWORD:
arg_delete_root_password = true;
break;
case '?': case '?':
return -EINVAL; return -EINVAL;
@ -935,6 +1116,10 @@ static int parse_argv(int argc, char *argv[]) {
if (arg_locale_messages && !locale_is_ok(arg_locale_messages)) if (arg_locale_messages && !locale_is_ok(arg_locale_messages))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Locale %s is not installed.", arg_locale_messages); return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Locale %s is not installed.", arg_locale_messages);
if (arg_delete_root_password && (arg_copy_root_password || arg_root_password || arg_prompt_root_password))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--delete-root-password cannot be combined with other root password options");
return 1; return 1;
} }
@ -980,6 +1165,10 @@ static int run(int argc, char *argv[]) {
if (r < 0) if (r < 0)
return r; return r;
r = process_kernel_cmdline();
if (r < 0)
return r;
return 0; return 0;
} }

View File

@ -0,0 +1,37 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "alloc-util.h"
#include "fd-util.h"
#include "fs-util.h"
#include "rm-rf.h"
#include "string-util.h"
#include "strv.h"
#include "tests.h"
#include "tmpfile-util.h"
#include "fuzz.h"
#include "xdg-autostart-service.h"
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
_cleanup_(unlink_tempfilep) char name[] = "/tmp/fuzz-xdg-desktop.XXXXXX";
_cleanup_close_ int fd = -1;
_cleanup_(xdg_autostart_service_freep) XdgAutostartService *service = NULL;
_cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL;
/* We don't want to fill the logs with messages about parse errors.
* Disable most logging if not running standalone */
if (!getenv("SYSTEMD_LOG_LEVEL"))
log_set_max_level(LOG_CRIT);
assert_se(mkdtemp_malloc("/tmp/fuzz-xdg-desktop-XXXXXX", &tmpdir) >= 0);
fd = mkostemp_safe(name);
assert_se(fd >= 0);
assert_se(write(fd, data, size) == (ssize_t) size);
assert_se(service = xdg_autostart_service_parse_desktop(name));
assert_se(service->name = strdup("fuzz-xdg-desktop.service"));
if (service)
(void) xdg_autostart_service_generate_unit(service, tmpdir);
return 0;
}

View File

@ -146,4 +146,10 @@ fuzzers += [
[['src/fuzz/fuzz-time-util.c'], [['src/fuzz/fuzz-time-util.c'],
[libshared], [libshared],
[]], []],
[['src/fuzz/fuzz-xdg-desktop.c',
'src/xdg-autostart-generator/xdg-autostart-service.h',
'src/xdg-autostart-generator/xdg-autostart-service.c'],
[],
[]],
] ]

View File

@ -465,8 +465,17 @@ static bool validate_runtime_directory(pam_handle_t *handle, const char *path, u
assert(handle); assert(handle);
assert(path); assert(path);
/* Just some extra paranoia: let's not set $XDG_RUNTIME_DIR if the directory we'd set it to isn't actually set /* Some extra paranoia: let's not set $XDG_RUNTIME_DIR if the directory we'd set it to isn't actually
* up properly for us. */ * set up properly for us. This is supposed to provide a careful safety net for supporting su/sudo
* type transitions: in that case the UID changes, but the session and thus the user owning it
* doesn't change. Since the $XDG_RUNTIME_DIR life-cycle is bound to the session's user being logged
* in at least once we should be particularly careful when setting the environment variable, since
* otherwise we might end up setting $XDG_RUNTIME_DIR to some directory owned by the wrong user. */
if (!path_is_absolute(path)) {
pam_syslog(handle, LOG_ERR, "Provided runtime directory '%s' is not absolute.", path);
goto fail;
}
if (lstat(path, &st) < 0) { if (lstat(path, &st) < 0) {
pam_syslog(handle, LOG_ERR, "Failed to stat() runtime directory '%s': %s", path, strerror_safe(errno)); pam_syslog(handle, LOG_ERR, "Failed to stat() runtime directory '%s': %s", path, strerror_safe(errno));
@ -622,6 +631,29 @@ static int apply_user_record_settings(pam_handle_t *handle, UserRecord *ur, bool
return PAM_SUCCESS; return PAM_SUCCESS;
} }
static int configure_runtime_directory(
pam_handle_t *handle,
UserRecord *ur,
const char *rt) {
int r;
assert(handle);
assert(ur);
assert(rt);
if (!validate_runtime_directory(handle, rt, ur->uid))
return PAM_SUCCESS;
r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
if (r != PAM_SUCCESS) {
pam_syslog(handle, LOG_ERR, "Failed to set runtime dir: %s", pam_strerror(handle, r));
return r;
}
return export_legacy_dbus_address(handle, rt);
}
_public_ PAM_EXTERN int pam_sm_open_session( _public_ PAM_EXTERN int pam_sm_open_session(
pam_handle_t *handle, pam_handle_t *handle,
int flags, int flags,
@ -677,15 +709,7 @@ _public_ PAM_EXTERN int pam_sm_open_session(
char rt[STRLEN("/run/user/") + DECIMAL_STR_MAX(uid_t)]; char rt[STRLEN("/run/user/") + DECIMAL_STR_MAX(uid_t)];
xsprintf(rt, "/run/user/"UID_FMT, ur->uid); xsprintf(rt, "/run/user/"UID_FMT, ur->uid);
if (validate_runtime_directory(handle, rt, ur->uid)) { r = configure_runtime_directory(handle, ur, rt);
r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
if (r != PAM_SUCCESS) {
pam_syslog(handle, LOG_ERR, "Failed to set runtime dir: %s", pam_strerror(handle, r));
return r;
}
}
r = export_legacy_dbus_address(handle, rt);
if (r != PAM_SUCCESS) if (r != PAM_SUCCESS)
return r; return r;
@ -875,19 +899,11 @@ _public_ PAM_EXTERN int pam_sm_open_session(
return r; return r;
if (original_uid == ur->uid) { if (original_uid == ur->uid) {
/* Don't set $XDG_RUNTIME_DIR if the user we now /* Don't set $XDG_RUNTIME_DIR if the user we now authenticated for does not match the
* authenticated for does not match the original user * original user of the session. We do this in order not to result in privileged apps
* of the session. We do this in order not to result * clobbering the runtime directory unnecessarily. */
* in privileged apps clobbering the runtime directory
* unnecessarily. */
if (validate_runtime_directory(handle, runtime_path, ur->uid)) { r = configure_runtime_directory(handle, ur, runtime_path);
r = update_environment(handle, "XDG_RUNTIME_DIR", runtime_path);
if (r != PAM_SUCCESS)
return r;
}
r = export_legacy_dbus_address(handle, runtime_path);
if (r != PAM_SUCCESS) if (r != PAM_SUCCESS)
return r; return r;
} }

View File

@ -25,6 +25,7 @@ static int dhcp6_lease_address_acquired(sd_dhcp6_client *client, Link *link);
static Link *dhcp6_prefix_get(Manager *m, struct in6_addr *addr); static Link *dhcp6_prefix_get(Manager *m, struct in6_addr *addr);
static int dhcp6_prefix_add(Manager *m, struct in6_addr *addr, Link *link); static int dhcp6_prefix_add(Manager *m, struct in6_addr *addr, Link *link);
static int dhcp6_prefix_remove_all(Manager *m, Link *link); static int dhcp6_prefix_remove_all(Manager *m, Link *link);
static bool dhcp6_link_has_dhcpv6_prefix(Link *link);
static bool dhcp6_get_prefix_delegation(Link *link) { static bool dhcp6_get_prefix_delegation(Link *link) {
if (!link->network) if (!link->network)
@ -35,6 +36,97 @@ static bool dhcp6_get_prefix_delegation(Link *link) {
RADV_PREFIX_DELEGATION_BOTH); RADV_PREFIX_DELEGATION_BOTH);
} }
static bool dhcp6_has_preferred_subnet_id(Link *link) {
if (!link->network)
return false;
return link->network->router_prefix_subnet_id >= 0;
}
static int dhcp6_get_preferred_delegated_prefix(
Manager* manager,
Link *link,
const struct in6_addr *pd_prefix,
uint8_t pd_prefix_len,
struct in6_addr *ret_addr) {
int r;
union in_addr_union pd_prefix_union = {
.in6 = *pd_prefix,
};
int64_t subnet_id = link->network->router_prefix_subnet_id;
assert(pd_prefix_len <= 64);
uint8_t prefix_bits = 64 - pd_prefix_len;
uint64_t n_prefixes = UINT64_C(1) << prefix_bits;
_cleanup_free_ char *assigned_buf = NULL;
/* We start off with the original PD prefix we have been assigned and
* iterate from there */
union in_addr_union prefix = {
.in6 = *pd_prefix,
};
if (subnet_id >= 0) {
/* If the link has a preference for a particular subnet id try to allocate that */
if ((uint64_t)subnet_id >= n_prefixes)
return log_link_debug_errno(link,
SYNTHETIC_ERRNO(ERANGE),
"subnet id %" PRIi64 " is out of range. Only have %" PRIu64 " subnets.",
subnet_id,
n_prefixes);
r = in_addr_prefix_nth(AF_INET6, &prefix, 64, subnet_id);
if (r < 0)
return log_link_debug_errno(link,
r,
"subnet id %" PRIi64 " is out of range. Only have %" PRIu64 " subnets.",
subnet_id,
n_prefixes);
/* Verify that the prefix we did calculate fits in the pd prefix.
* This should not fail as we checked the prefix size beforehand */
assert_se(in_addr_prefix_covers(AF_INET6, &pd_prefix_union, pd_prefix_len, &prefix) > 0);
Link* assigned_link = dhcp6_prefix_get(manager, &prefix.in6);
(void) in_addr_to_string(AF_INET6, &prefix, &assigned_buf);
if (assigned_link && assigned_link != link)
return log_link_error_errno(link, SYNTHETIC_ERRNO(EAGAIN),
"The requested prefix %s is already assigned to another link: %s",
strnull(assigned_buf),
strnull(assigned_link->ifname));
*ret_addr = prefix.in6;
log_link_debug(link, "The requested prefix %s is available. Using it.",
strnull(assigned_buf));
return 0;
} else {
for (uint64_t n = 0; n < n_prefixes; n++) {
/* if we do not have an allocation preference just iterate
* through the address space and return the first free prefix. */
Link* assigned_link = dhcp6_prefix_get(manager, &prefix.in6);
if (!assigned_link || assigned_link == link) {
*ret_addr = prefix.in6;
return 0;
}
r = in_addr_prefix_next(AF_INET6, &prefix, 64);
if (r < 0)
return log_link_error_errno(link,
r,
"Can't allocate another prefix. Out of address space?");
}
log_link_warning(link, "Couldn't find a suitable prefix. Ran out of address space.");
}
return -ERANGE;
}
static bool dhcp6_enable_prefix_delegation(Link *dhcp6_link) { static bool dhcp6_enable_prefix_delegation(Link *dhcp6_link) {
Manager *manager; Manager *manager;
Link *l; Link *l;
@ -165,24 +257,27 @@ int dhcp6_lease_pd_prefix_lost(sd_dhcp6_client *client, Link* link) {
return 0; return 0;
} }
static int dhcp6_pd_prefix_distribute(Link *dhcp6_link, Iterator *i, static int dhcp6_pd_prefix_distribute(Link *dhcp6_link,
struct in6_addr *pd_prefix, struct in6_addr *pd_prefix,
uint8_t pd_prefix_len, uint8_t pd_prefix_len,
uint32_t lifetime_preferred, uint32_t lifetime_preferred,
uint32_t lifetime_valid) { uint32_t lifetime_valid,
bool assign_preferred_subnet_id) {
Iterator i;
Link *link; Link *link;
Manager *manager = dhcp6_link->manager; Manager *manager = dhcp6_link->manager;
union in_addr_union prefix; union in_addr_union prefix = {
uint64_t n_prefixes, n_used = 0; .in6 = *pd_prefix,
};
uint64_t n_prefixes;
_cleanup_free_ char *buf = NULL; _cleanup_free_ char *buf = NULL;
_cleanup_free_ char *assigned_buf = NULL; _cleanup_free_ char *assigned_buf = NULL;
int r; int r;
bool pool_depleted = false;
assert(manager); assert(manager);
assert(pd_prefix_len <= 64); assert(pd_prefix_len <= 64);
prefix.in6 = *pd_prefix;
r = in_addr_mask(AF_INET6, &prefix, pd_prefix_len); r = in_addr_mask(AF_INET6, &prefix, pd_prefix_len);
if (r < 0) if (r < 0)
return r; return r;
@ -193,15 +288,8 @@ static int dhcp6_pd_prefix_distribute(Link *dhcp6_link, Iterator *i,
log_link_debug(dhcp6_link, "Assigning up to %" PRIu64 " prefixes from %s/%u", log_link_debug(dhcp6_link, "Assigning up to %" PRIu64 " prefixes from %s/%u",
n_prefixes, strnull(buf), pd_prefix_len); n_prefixes, strnull(buf), pd_prefix_len);
while (hashmap_iterate(manager->links, i, (void **)&link, NULL)) { HASHMAP_FOREACH(link, manager->links, i) {
Link *assigned_link; union in_addr_union assigned_prefix;
if (n_used == n_prefixes) {
log_link_debug(dhcp6_link, "Assigned %" PRIu64 "/%" PRIu64 " prefixes from %s/%u",
n_used, n_prefixes, strnull(buf), pd_prefix_len);
return -EAGAIN;
}
if (link == dhcp6_link) if (link == dhcp6_link)
continue; continue;
@ -209,35 +297,42 @@ static int dhcp6_pd_prefix_distribute(Link *dhcp6_link, Iterator *i,
if (!dhcp6_get_prefix_delegation(link)) if (!dhcp6_get_prefix_delegation(link))
continue; continue;
assigned_link = dhcp6_prefix_get(manager, &prefix.in6); if (dhcp6_link_has_dhcpv6_prefix(link))
if (assigned_link && assigned_link != link)
continue; continue;
(void) in_addr_to_string(AF_INET6, &prefix, &assigned_buf); if (assign_preferred_subnet_id != dhcp6_has_preferred_subnet_id(link))
r = dhcp6_pd_prefix_assign(link, &prefix.in6, 64, continue;
r = dhcp6_get_preferred_delegated_prefix(manager, link, &prefix.in6, pd_prefix_len,
&assigned_prefix.in6);
if (assign_preferred_subnet_id && r == -EAGAIN) {
/* A link has a preferred subnet_id but that one is
* already taken by another link. Now all the remaining
* links will also not obtain a prefix. */
pool_depleted = true;
continue;
} else if (r < 0)
return r;
(void) in_addr_to_string(AF_INET6, &assigned_prefix, &assigned_buf);
r = dhcp6_pd_prefix_assign(link, &assigned_prefix.in6, 64,
lifetime_preferred, lifetime_valid); lifetime_preferred, lifetime_valid);
if (r < 0) { if (r < 0) {
log_link_error_errno(link, r, "Unable to %s prefix %s/64 from %s/%u for link: %m", log_link_error_errno(link, r, "Unable to assign/update prefix %s/64 from %s/%u for link: %m",
assigned_link ? "update": "assign",
strnull(assigned_buf), strnull(assigned_buf),
strnull(buf), pd_prefix_len); strnull(buf), pd_prefix_len);
if (!assigned_link)
continue;
} else } else
log_link_debug(link, "Assigned prefix %" PRIu64 "/%" PRIu64 " %s/64 from %s/%u to link", log_link_debug(link, "Assigned prefix %s/64 from %s/%u to link",
n_used + 1, n_prefixes,
strnull(assigned_buf), strnull(assigned_buf),
strnull(buf), pd_prefix_len); strnull(buf), pd_prefix_len);
n_used++;
r = in_addr_prefix_next(AF_INET6, &prefix, 64);
if (r < 0 && n_used < n_prefixes)
return r;
} }
/* If one of the link requests couldn't be fulfilled, signal that we
should try again with another prefix. */
if (pool_depleted)
return -EAGAIN;
return 0; return 0;
} }
@ -262,7 +357,6 @@ static int dhcp6_lease_pd_prefix_acquired(sd_dhcp6_client *client, Link *link) {
union in_addr_union pd_prefix; union in_addr_union pd_prefix;
uint8_t pd_prefix_len; uint8_t pd_prefix_len;
uint32_t lifetime_preferred, lifetime_valid; uint32_t lifetime_preferred, lifetime_valid;
Iterator i = ITERATOR_FIRST;
r = sd_dhcp6_client_get_lease(client, &lease); r = sd_dhcp6_client_get_lease(client, &lease);
if (r < 0) if (r < 0)
@ -314,15 +408,47 @@ static int dhcp6_lease_pd_prefix_acquired(sd_dhcp6_client *client, Link *link) {
} else } else
log_link_debug(link, "Not adding a blocking route since distributed prefix is /64"); log_link_debug(link, "Not adding a blocking route since distributed prefix is /64");
r = dhcp6_pd_prefix_distribute(link, &i, &pd_prefix.in6, /* We are doing prefix allocation in two steps:
* 1. all those links that have a preferred subnet id will be assigned their subnet
* 2. all those links that remain will receive prefixes in sequential
* order. Prefixes that were previously already allocated to another
* link will be skipped.
* If a subnet id request couldn't be fullfilled the failure will be logged (as error)
* and no further attempts at obtaining a prefix will be made.
* The assignment has to be split in two phases since subnet id
* preferences should be honored. Meaning that any subnet id should be
* handed out to the requesting link and not to some link that didn't
* specify any preference. */
r = dhcp6_pd_prefix_distribute(link, &pd_prefix.in6,
pd_prefix_len, pd_prefix_len,
lifetime_preferred, lifetime_preferred,
lifetime_valid); lifetime_valid,
true);
if (r < 0 && r != -EAGAIN) if (r < 0 && r != -EAGAIN)
return r; return r;
if (r >= 0) /* if r == -EAGAIN then the allocation failed because we ran
i = ITERATOR_FIRST; * out of addresses for the preferred subnet id's. This doesn't
* mean we can't fullfill other prefix requests.
*
* Since we do not have dedicated lists of links that request
* specific subnet id's and those that accept any prefix we
* *must* reset the iterator to the start as otherwise some
* links might not get their requested prefix. */
r = dhcp6_pd_prefix_distribute(link, &pd_prefix.in6,
pd_prefix_len,
lifetime_preferred,
lifetime_valid,
false);
if (r < 0 && r != -EAGAIN)
return r;
/* If the prefix distribution did return -EAGAIN we will try to
* fullfill those with the next available pd delegated prefix. */
} }
return 0; return 0;
@ -878,3 +1004,17 @@ static int dhcp6_prefix_remove_all(Manager *m, Link *link) {
return 0; return 0;
} }
static bool dhcp6_link_has_dhcpv6_prefix(Link *link) {
Iterator i;
Link *l;
assert(link);
assert(link->manager);
HASHMAP_FOREACH(l, link->manager->dhcp6_prefixes, i)
if (link == l)
return true;
return false;
}

View File

@ -250,6 +250,7 @@ BridgeVLAN.PVID, config_parse_brvlan_pvid,
BridgeVLAN.VLAN, config_parse_brvlan_vlan, 0, 0 BridgeVLAN.VLAN, config_parse_brvlan_vlan, 0, 0
BridgeVLAN.EgressUntagged, config_parse_brvlan_untagged, 0, 0 BridgeVLAN.EgressUntagged, config_parse_brvlan_untagged, 0, 0
Network.IPv6PrefixDelegation, config_parse_router_prefix_delegation, 0, 0 Network.IPv6PrefixDelegation, config_parse_router_prefix_delegation, 0, 0
Network.IPv6PDSubnetId, config_parse_router_prefix_subnet_id, 0, 0
IPv6PrefixDelegation.RouterLifetimeSec, config_parse_sec, 0, offsetof(Network, router_lifetime_usec) IPv6PrefixDelegation.RouterLifetimeSec, config_parse_sec, 0, offsetof(Network, router_lifetime_usec)
IPv6PrefixDelegation.Managed, config_parse_bool, 0, offsetof(Network, router_managed) IPv6PrefixDelegation.Managed, config_parse_bool, 0, offsetof(Network, router_managed)
IPv6PrefixDelegation.OtherInformation, config_parse_bool, 0, offsetof(Network, router_other_information) IPv6PrefixDelegation.OtherInformation, config_parse_bool, 0, offsetof(Network, router_other_information)

View File

@ -415,6 +415,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi
.dhcp_server_emit_router = true, .dhcp_server_emit_router = true,
.dhcp_server_emit_timezone = true, .dhcp_server_emit_timezone = true,
.router_prefix_subnet_id = -1,
.router_emit_dns = true, .router_emit_dns = true,
.router_emit_domains = true, .router_emit_domains = true,

View File

@ -178,6 +178,7 @@ struct Network {
/* IPv6 prefix delegation support */ /* IPv6 prefix delegation support */
RADVPrefixDelegation router_prefix_delegation; RADVPrefixDelegation router_prefix_delegation;
int64_t router_prefix_subnet_id;
usec_t router_lifetime_usec; usec_t router_lifetime_usec;
uint8_t router_preference; uint8_t router_preference;
bool router_managed; bool router_managed;

View File

@ -864,3 +864,40 @@ int config_parse_router_preference(const char *unit,
return 0; return 0;
} }
int config_parse_router_prefix_subnet_id(const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
Network *network = userdata;
uint64_t t;
int r;
assert(filename);
assert(lvalue);
assert(rvalue);
assert(data);
if (isempty(rvalue) || streq(rvalue, "auto")) {
network->router_prefix_subnet_id = -1;
return 0;
}
r = safe_atoux64(rvalue, &t);
if (r < 0 || t > INT64_MAX) {
log_syntax(unit, LOG_ERR, filename, line, r,
"Subnet id '%s' is invalid, ignoring assignment.",
rvalue);
return 0;
}
network->router_prefix_subnet_id = (int64_t)t;
return 0;
}

View File

@ -58,6 +58,7 @@ RADVPrefixDelegation radv_prefix_delegation_from_string(const char *s) _pure_;
CONFIG_PARSER_PROTOTYPE(config_parse_router_prefix_delegation); CONFIG_PARSER_PROTOTYPE(config_parse_router_prefix_delegation);
CONFIG_PARSER_PROTOTYPE(config_parse_router_preference); CONFIG_PARSER_PROTOTYPE(config_parse_router_preference);
CONFIG_PARSER_PROTOTYPE(config_parse_router_prefix_subnet_id);
CONFIG_PARSER_PROTOTYPE(config_parse_prefix); CONFIG_PARSER_PROTOTYPE(config_parse_prefix);
CONFIG_PARSER_PROTOTYPE(config_parse_prefix_flags); CONFIG_PARSER_PROTOTYPE(config_parse_prefix_flags);
CONFIG_PARSER_PROTOTYPE(config_parse_prefix_lifetime); CONFIG_PARSER_PROTOTYPE(config_parse_prefix_lifetime);

View File

@ -345,28 +345,6 @@ static int putsgent_with_members(const struct sgrp *sg, FILE *gshadow) {
} }
#endif #endif
static int sync_rights(FILE *from, const char *to) {
struct stat st;
if (fstat(fileno(from), &st) < 0)
return -errno;
return chmod_and_chown_unsafe(to, st.st_mode & 07777, st.st_uid, st.st_gid);
}
static int rename_and_apply_smack(const char *temp_path, const char *dest_path) {
int r = 0;
if (rename(temp_path, dest_path) < 0)
return -errno;
#ifdef SMACK_RUN_LABEL
r = mac_smack_apply(dest_path, SMACK_ATTR_ACCESS, SMACK_FLOOR_LABEL);
if (r < 0)
return r;
#endif
return r;
}
static const char* default_shell(uid_t uid) { static const char* default_shell(uid_t uid) {
return uid == 0 ? "/bin/sh" : NOLOGIN; return uid == 0 ? "/bin/sh" : NOLOGIN;
} }
@ -389,7 +367,7 @@ static int write_temporary_passwd(const char *passwd_path, FILE **tmpfile, char
original = fopen(passwd_path, "re"); original = fopen(passwd_path, "re");
if (original) { if (original) {
r = sync_rights(original, passwd_tmp); r = sync_rights(fileno(original), fileno(passwd));
if (r < 0) if (r < 0)
return r; return r;
@ -491,7 +469,7 @@ static int write_temporary_shadow(const char *shadow_path, FILE **tmpfile, char
original = fopen(shadow_path, "re"); original = fopen(shadow_path, "re");
if (original) { if (original) {
r = sync_rights(original, shadow_tmp); r = sync_rights(fileno(original), fileno(shadow));
if (r < 0) if (r < 0)
return r; return r;
@ -588,7 +566,7 @@ static int write_temporary_group(const char *group_path, FILE **tmpfile, char **
original = fopen(group_path, "re"); original = fopen(group_path, "re");
if (original) { if (original) {
r = sync_rights(original, group_tmp); r = sync_rights(fileno(original), fileno(group));
if (r < 0) if (r < 0)
return r; return r;
@ -687,7 +665,7 @@ static int write_temporary_gshadow(const char * gshadow_path, FILE **tmpfile, ch
if (original) { if (original) {
struct sgrp *sg; struct sgrp *sg;
r = sync_rights(original, gshadow_tmp); r = sync_rights(fileno(original), fileno(gshadow));
if (r < 0) if (r < 0)
return r; return r;
@ -794,14 +772,14 @@ static int write_files(void) {
/* And make the new files count */ /* And make the new files count */
if (group) { if (group) {
r = rename_and_apply_smack(group_tmp, group_path); r = rename_and_apply_smack_floor_label(group_tmp, group_path);
if (r < 0) if (r < 0)
return r; return r;
group_tmp = mfree(group_tmp); group_tmp = mfree(group_tmp);
} }
if (gshadow) { if (gshadow) {
r = rename_and_apply_smack(gshadow_tmp, gshadow_path); r = rename_and_apply_smack_floor_label(gshadow_tmp, gshadow_path);
if (r < 0) if (r < 0)
return r; return r;
@ -809,14 +787,14 @@ static int write_files(void) {
} }
if (passwd) { if (passwd) {
r = rename_and_apply_smack(passwd_tmp, passwd_path); r = rename_and_apply_smack_floor_label(passwd_tmp, passwd_path);
if (r < 0) if (r < 0)
return r; return r;
passwd_tmp = mfree(passwd_tmp); passwd_tmp = mfree(passwd_tmp);
} }
if (shadow) { if (shadow) {
r = rename_and_apply_smack(shadow_tmp, shadow_path); r = rename_and_apply_smack_floor_label(shadow_tmp, shadow_path);
if (r < 0) if (r < 0)
return r; return r;

View File

@ -849,7 +849,7 @@ static int set_dependencies_from_rcnd(const LookupPaths *lp, Hashmap *all_servic
continue; continue;
} }
FOREACH_DIRENT(de, d, log_error_errno(errno, "Failed to enumerate directory %s, ignoring: %m", path)) { FOREACH_DIRENT(de, d, log_warning_errno(errno, "Failed to enumerate directory %s, ignoring: %m", path)) {
_cleanup_free_ char *name = NULL, *fpath = NULL; _cleanup_free_ char *name = NULL, *fpath = NULL;
int a, b; int a, b;

View File

@ -1143,3 +1143,13 @@ tests += [
libshared], libshared],
[threads]], [threads]],
] ]
############################################################
tests += [
[['src/test/test-xdg-autostart.c',
'src/xdg-autostart-generator/xdg-autostart-service.c',
'src/xdg-autostart-generator/xdg-autostart-service.h',],
[],
[]],
]

View File

@ -173,6 +173,45 @@ static void test_in_addr_prefix_next(void) {
test_in_addr_prefix_next_one(AF_INET6, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff00", 120, NULL); test_in_addr_prefix_next_one(AF_INET6, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff00", 120, NULL);
} }
static void test_in_addr_prefix_nth_one(unsigned f, const char *before, unsigned pl, uint64_t nth, const char *after) {
union in_addr_union ubefore, uafter, t;
assert_se(in_addr_from_string(f, before, &ubefore) >= 0);
t = ubefore;
assert_se((in_addr_prefix_nth(f, &t, pl, nth) > 0) == !!after);
if (after) {
assert_se(in_addr_from_string(f, after, &uafter) >= 0);
assert_se(in_addr_equal(f, &t, &uafter) > 0);
}
}
static void test_in_addr_prefix_nth(void) {
log_info("/* %s */", __func__);
test_in_addr_prefix_nth_one(AF_INET, "192.168.0.0", 24, 0, "192.168.0.0");
test_in_addr_prefix_nth_one(AF_INET, "192.168.0.0", 24, 1, "192.168.1.0");
test_in_addr_prefix_nth_one(AF_INET, "192.168.0.0", 24, 4, "192.168.4.0");
test_in_addr_prefix_nth_one(AF_INET, "192.168.0.0", 25, 1, "192.168.0.128");
test_in_addr_prefix_nth_one(AF_INET, "192.168.255.0", 25, 1, "192.168.255.128");
test_in_addr_prefix_nth_one(AF_INET, "192.168.255.0", 24, 0, "192.168.255.0");
test_in_addr_prefix_nth_one(AF_INET, "255.255.255.255", 32, 1, NULL);
test_in_addr_prefix_nth_one(AF_INET, "255.255.255.255", 0, 1, NULL);
test_in_addr_prefix_nth_one(AF_INET6, "4400::", 8, 1, "4500::");
test_in_addr_prefix_nth_one(AF_INET6, "4400::", 7, 1, "4600::");
test_in_addr_prefix_nth_one(AF_INET6, "4400::", 64, 1, "4400:0:0:1::");
test_in_addr_prefix_nth_one(AF_INET6, "4400::", 64, 2, "4400:0:0:2::");
test_in_addr_prefix_nth_one(AF_INET6, "4400::", 64, 0xbad, "4400:0:0:0bad::");
test_in_addr_prefix_nth_one(AF_INET6, "4400:0:0:ffff::", 64, 1, "4400:0:1::");
test_in_addr_prefix_nth_one(AF_INET6, "4400::", 56, ((uint64_t)1<<48) -1, "44ff:ffff:ffff:ff00::");
test_in_addr_prefix_nth_one(AF_INET6, "0000::", 8, 255, "ff00::");
test_in_addr_prefix_nth_one(AF_INET6, "0000::", 8, 256, NULL);
test_in_addr_prefix_nth_one(AF_INET6, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 128, 1, NULL);
test_in_addr_prefix_nth_one(AF_INET6, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 0, 1, NULL);
}
static void test_in_addr_to_string_one(int f, const char *addr) { static void test_in_addr_to_string_one(int f, const char *addr) {
union in_addr_union ua; union in_addr_union ua;
_cleanup_free_ char *r = NULL; _cleanup_free_ char *r = NULL;
@ -691,6 +730,7 @@ int main(int argc, char *argv[]) {
test_in_addr_is_null(); test_in_addr_is_null();
test_in_addr_prefix_intersect(); test_in_addr_prefix_intersect();
test_in_addr_prefix_next(); test_in_addr_prefix_next();
test_in_addr_prefix_nth();
test_in_addr_to_string(); test_in_addr_to_string();
test_in_addr_ifindex_to_string(); test_in_addr_ifindex_to_string();
test_in_addr_ifindex_from_string_auto(); test_in_addr_ifindex_from_string_auto();

View File

@ -0,0 +1,95 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "alloc-util.h"
#include "fd-util.h"
#include "fs-util.h"
#include "string-util.h"
#include "strv.h"
#include "tests.h"
#include "tmpfile-util.h"
#include "xdg-autostart-service.h"
static void test_translate_name(void) {
_cleanup_free_ char *t;
assert_se(t = xdg_autostart_service_translate_name("a-b.blub.desktop"));
assert_se(streq(t, "app-a\\x2db.blub-autostart.service"));
}
static void test_xdg_format_exec_start_one(const char *exec, const char *expected) {
_cleanup_free_ char* out = NULL;
xdg_autostart_format_exec_start(exec, &out);
log_info("In: '%s', out: '%s', expected: '%s'", exec, out, expected);
assert_se(streq(out, expected));
}
static void test_xdg_format_exec_start(void) {
test_xdg_format_exec_start_one("/bin/sleep 100", "/bin/sleep \"100\"");
/* All standardised % identifiers are stripped. */
test_xdg_format_exec_start_one("/bin/sleep %f \"%F\" %u %U %d %D\t%n %N %i %c %k %v %m", "/bin/sleep");
/* Unknown % identifier currently remain, but are escaped. */
test_xdg_format_exec_start_one("/bin/sleep %X \"%Y\"", "/bin/sleep \"%%X\" \"%%Y\"");
test_xdg_format_exec_start_one("/bin/sleep \";\\\"\"", "/bin/sleep \";\\\"\"");
}
static const char* const xdg_desktop_file[] = {
"[Desktop Entry]\n"
"Exec\t =\t /bin/sleep 100\n" /* Whitespace Before/After = must be ignored */
"OnlyShowIn = A;B;\n"
"NotShowIn=C;;D\\\\\\;;E\n", /* "C", "", "D\;", "E" */
"[Desktop Entry]\n"
"Exec=a\n"
"Exec=b\n",
"[Desktop Entry]\n"
"Hidden=\t true\n",
};
static void test_xdg_desktop_parse(unsigned i, const char *s) {
_cleanup_(unlink_tempfilep) char name[] = "/tmp/test-xdg-autostart-parser.XXXXXX";
_cleanup_fclose_ FILE *f = NULL;
_cleanup_(xdg_autostart_service_freep) XdgAutostartService *service = NULL;
log_info("== %s[%i] ==", __func__, i);
assert_se(fmkostemp_safe(name, "r+", &f) == 0);
assert_se(fwrite(s, strlen(s), 1, f) == 1);
rewind(f);
assert_se(service = xdg_autostart_service_parse_desktop(name));
switch (i) {
case 0:
assert_se(streq(service->exec_string, "/bin/sleep 100"));
assert_se(strv_equal(service->only_show_in, STRV_MAKE("A", "B")));
assert_se(strv_equal(service->not_show_in, STRV_MAKE("C", "", "D\\;", "E")));
assert_se(!service->hidden);
break;
case 1:
/* The second entry is not permissible and will be ignored (and error logged). */
assert_se(streq(service->exec_string, "a"));
break;
case 2:
assert_se(service->hidden);
break;
}
}
int main(int argc, char *argv[]) {
size_t i;
test_setup_logging(LOG_DEBUG);
test_translate_name();
test_xdg_format_exec_start();
for (i = 0; i < ELEMENTSOF(xdg_desktop_file); i++)
test_xdg_desktop_parse(i, xdg_desktop_file[i]);
return 0;
}

View File

@ -0,0 +1,46 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "main-func.h"
#include "strv.h"
/*
* This binary is intended to be run as an ExecCondition= in units generated
* by the xdg-autostart-generator. It does the appropriate checks against
* XDG_CURRENT_DESKTOP that are too advanced for simple ConditionEnvironment=
* matches.
*/
static int run(int argc, char *argv[]) {
_cleanup_strv_free_ char **only_show_in = NULL, **not_show_in = NULL, **desktops = NULL;
const char *xdg_current_desktop;
char **d;
if (argc != 3)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Wrong argument count. Expected the OnlyShowIn= and NotShowIn= sets, each colon separated.");
xdg_current_desktop = getenv("XDG_CURRENT_DESKTOP");
if (xdg_current_desktop) {
desktops = strv_split(xdg_current_desktop, ":");
if (!desktops)
return log_oom();
}
only_show_in = strv_split(argv[1], ":");
not_show_in = strv_split(argv[2], ":");
if (!only_show_in || !not_show_in)
return log_oom();
/* Each desktop in XDG_CURRENT_DESKTOP needs to be matched in order. */
STRV_FOREACH(d, desktops) {
if (strv_contains(only_show_in, *d))
return 0;
if (strv_contains(not_show_in, *d))
return 1;
}
/* non-zero exit code when only_show_in has a proper value */
return !strv_isempty(only_show_in);
}
DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);

View File

@ -0,0 +1,116 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include "dirent-util.h"
#include "fd-util.h"
#include "generator.h"
#include "hashmap.h"
#include "log.h"
#include "main-func.h"
#include "nulstr-util.h"
#include "path-lookup.h"
#include "stat-util.h"
#include "string-util.h"
#include "strv.h"
#include "xdg-autostart-service.h"
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(xdgautostartservice_hash_ops, char, string_hash_func, string_compare_func, XdgAutostartService, xdg_autostart_service_free);
static int enumerate_xdg_autostart(Hashmap *all_services) {
_cleanup_strv_free_ char **autostart_dirs = NULL;
_cleanup_strv_free_ char **config_dirs = NULL;
_unused_ _cleanup_strv_free_ char **data_dirs = NULL;
_cleanup_free_ char *user_config_autostart_dir = NULL;
char **path;
int r;
r = xdg_user_config_dir(&user_config_autostart_dir, "/autostart");
if (r < 0)
return r;
r = strv_extend(&autostart_dirs, user_config_autostart_dir);
if (r < 0)
return r;
r = xdg_user_dirs(&config_dirs, &data_dirs);
if (r < 0)
return r;
r = strv_extend_strv_concat(&autostart_dirs, config_dirs, "/autostart");
if (r < 0)
return r;
STRV_FOREACH(path, autostart_dirs) {
_cleanup_closedir_ DIR *d = NULL;
struct dirent *de;
d = opendir(*path);
if (!d) {
if (errno != ENOENT)
log_warning_errno(errno, "Opening %s failed, ignoring: %m", *path);
continue;
}
FOREACH_DIRENT(de, d, log_warning_errno(errno, "Failed to enumerate directory %s, ignoring: %m", *path)) {
_cleanup_free_ char *fpath = NULL, *name = NULL;
_cleanup_(xdg_autostart_service_freep) XdgAutostartService *service = NULL;
struct stat st;
if (fstatat(dirfd(d), de->d_name, &st, 0) < 0) {
log_warning_errno(errno, "stat() failed on %s/%s, ignoring: %m", *path, de->d_name);
continue;
}
if (!S_ISREG(st.st_mode))
continue;
name = xdg_autostart_service_translate_name(de->d_name);
if (!name)
return log_oom();
if (hashmap_contains(all_services, name))
continue;
fpath = path_join(*path, de->d_name);
if (!fpath)
return log_oom();
service = xdg_autostart_service_parse_desktop(fpath);
if (!service)
return log_oom();
service->name = TAKE_PTR(name);
r = hashmap_put(all_services, service->name, service);
if (r < 0)
return log_oom();
TAKE_PTR(service);
}
}
return 0;
}
static int run(const char *dest, const char *dest_early, const char *dest_late) {
_cleanup_(hashmap_freep) Hashmap *all_services = NULL;
XdgAutostartService *service;
Iterator j;
int r;
assert_se(dest_late);
all_services = hashmap_new(&xdgautostartservice_hash_ops);
if (!all_services)
return log_oom();
r = enumerate_xdg_autostart(all_services);
if (r < 0)
return r;
HASHMAP_FOREACH(service, all_services, j)
(void) xdg_autostart_service_generate_unit(service, dest_late);
return 0;
}
DEFINE_MAIN_GENERATOR_FUNCTION(run);

View File

@ -0,0 +1,618 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include "xdg-autostart-service.h"
#include "conf-parser.h"
#include "escape.h"
#include "unit-name.h"
#include "path-util.h"
#include "fd-util.h"
#include "generator.h"
#include "log.h"
#include "specifier.h"
#include "string-util.h"
#include "nulstr-util.h"
#include "strv.h"
XdgAutostartService* xdg_autostart_service_free(XdgAutostartService *s) {
if (!s)
return NULL;
free(s->name);
free(s->path);
free(s->description);
free(s->type);
free(s->exec_string);
strv_free(s->only_show_in);
strv_free(s->not_show_in);
free(s->try_exec);
free(s->autostart_condition);
free(s->kde_autostart_condition);
free(s->gnome_autostart_phase);
return mfree(s);
}
char *xdg_autostart_service_translate_name(const char *name) {
_cleanup_free_ char *c = NULL, *escaped = NULL;
char *res;
c = strdup(name);
if (!c)
return NULL;
res = endswith(c, ".desktop");
if (res)
*res = '\0';
escaped = unit_name_escape(c);
if (!escaped)
return NULL;
return strjoin("app-", escaped, "-autostart.service");
}
static int xdg_config_parse_bool(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
bool *b = data;
const char *value;
assert(filename);
assert(lvalue);
assert(rvalue);
assert(data);
if (streq(rvalue, "true"))
*b = true;
else if (streq(rvalue, "false"))
*b = false;
else
return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL), "Invalid value for boolean: %s", value);
return 0;
}
/* Unescapes the string in-place, returns non-zero status on error. */
static int xdg_unescape_string(
const char *unit,
const char *filename,
int line,
char *str) {
char *in;
char *out;
assert(str);
in = out = str;
for (; *in; in++, out++) {
if (*in == '\\') {
/* Move forward, and ensure it is a valid escape. */
in++;
switch (*in) {
case 's':
*out = ' ';
break;
case 'n':
*out = '\n';
break;
case 't':
*out = '\t';
break;
case 'r':
*out = '\r';
break;
case '\\':
*out = '\\';
break;
case ';':
/* Technically only permitted for strv. */
*out = ';';
break;
default:
return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL), "Undefined escape sequence \\%c.", *in);
}
continue;
}
*out = *in;
}
*out = '\0';
return 0;
}
/* Note: We do not bother with unescaping the strings, hence the _raw postfix. */
static int xdg_config_parse_string(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
_cleanup_free_ char *res = NULL;
char **out = data;
int r;
assert(filename);
assert(lvalue);
assert(rvalue);
assert(data);
/* XDG does not allow duplicate definitions. */
if (*out) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Key %s was defined multiple times, ignoring.", lvalue);
return 0;
}
res = strdup(rvalue);
if (!res)
return log_oom();
r = xdg_unescape_string(unit, filename, line, res);
if (r < 0)
return r;
*out = TAKE_PTR(res);
return 0;
}
static int xdg_config_parse_strv(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
char ***sv = data;
const char *start;
const char *end;
int r;
assert(filename);
assert(lvalue);
assert(rvalue);
assert(data);
/* XDG does not allow duplicate definitions. */
if (*sv) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Key %s was defined multiple times, ignoring.", lvalue);
return 0;
}
*sv = strv_new(NULL);
if (!*sv)
return log_oom();
/* We cannot use strv_split because it does not handle escaping correctly. */
start = rvalue;
for (end = start; *end; end++) {
if (*end == '\\') {
/* Move forward, and ensure it is a valid escape. */
end++;
if (strchr("sntr\\;", *end) == NULL) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Undefined escape sequence \\%c.", *end);
return 0;
}
continue;
}
if (*end == ';') {
_cleanup_free_ char *copy = NULL;
copy = strndup(start, end - start);
if (!copy)
return log_oom();
r = xdg_unescape_string(unit, filename, line, copy);
if (r < 0)
return r;
r = strv_consume(sv, TAKE_PTR(copy));
if (r < 0)
return log_oom();
start = end + 1;
}
}
/* Any trailing entry should be ignored if it is empty. */
if (end > start) {
r = strv_extend(sv, start);
if (r < 0)
return log_oom();
}
return 0;
}
static int xdg_config_item_table_lookup(
const void *table,
const char *section,
const char *lvalue,
ConfigParserCallback *func,
int *ltype,
void **data,
void *userdata) {
assert(lvalue);
/* Ignore any keys with [] as those are translations. */
if (strchr(lvalue, '[')) {
*func = NULL;
*ltype = 0;
*data = NULL;
return 1;
}
return config_item_table_lookup(table, section, lvalue, func, ltype, data, userdata);
}
XdgAutostartService *xdg_autostart_service_parse_desktop(const char *path) {
_cleanup_(xdg_autostart_service_freep) XdgAutostartService *service = NULL;
int r;
service = new0(XdgAutostartService, 1);
if (!service)
return NULL;
service->path = strdup(path);
if (!service->path)
return NULL;
const ConfigTableItem items[] = {
{ "Desktop Entry", "Name", xdg_config_parse_string, 0, &service->description},
{ "Desktop Entry", "Exec", xdg_config_parse_string, 0, &service->exec_string},
{ "Desktop Entry", "TryExec", xdg_config_parse_string, 0, &service->try_exec},
{ "Desktop Entry", "Type", xdg_config_parse_string, 0, &service->type},
{ "Desktop Entry", "OnlyShowIn", xdg_config_parse_strv, 0, &service->only_show_in},
{ "Desktop Entry", "NotShowIn", xdg_config_parse_strv, 0, &service->not_show_in},
{ "Desktop Entry", "Hidden", xdg_config_parse_bool, 0, &service->hidden},
{ "Desktop Entry", "AutostartCondition", xdg_config_parse_string, 0, &service->autostart_condition},
{ "Desktop Entry", "X-KDE-autostart-condition", xdg_config_parse_string, 0, &service->kde_autostart_condition},
{ "Desktop Entry", "X-GNOME-Autostart-Phase", xdg_config_parse_string, 0, &service->gnome_autostart_phase},
{ "Desktop Entry", "X-systemd-skip", xdg_config_parse_bool, 0, &service->systemd_skip},
/* Common entries that we do not use currently. */
{ "Desktop Entry", "Categories", NULL, 0, NULL},
{ "Desktop Entry", "Comment", NULL, 0, NULL},
{ "Desktop Entry", "Encoding", NULL, 0, NULL},
{ "Desktop Entry", "GenericName", NULL, 0, NULL},
{ "Desktop Entry", "Icon", NULL, 0, NULL},
{ "Desktop Entry", "Keywords", NULL, 0, NULL},
{ "Desktop Entry", "NoDisplay", NULL, 0, NULL},
{ "Desktop Entry", "StartupNotify", NULL, 0, NULL},
{ "Desktop Entry", "Terminal", NULL, 0, NULL},
{ "Desktop Entry", "Version", NULL, 0, NULL},
{}
};
r = config_parse(NULL, service->path, NULL,
"Desktop Entry\0",
xdg_config_item_table_lookup, items,
CONFIG_PARSE_WARN, service);
/* If parsing failed, only hide the file so it will still mask others. */
if (r < 0) {
log_warning_errno(r, "Failed to parse %s, ignoring it", service->path);
service->hidden = true;
}
return TAKE_PTR(service);
}
int xdg_autostart_format_exec_start(
const char *exec,
char **ret_exec_start) {
_cleanup_strv_free_ char **exec_split = NULL;
char *res;
size_t n, i;
bool first_arg;
int r;
/*
* Unfortunately, there is a mismatch between systemd's idea of $PATH
* and XDGs. i.e. we need to ensure that we have an absolute path to
* support cases where $PATH has been modified from the default set.
*
* Note that this is only needed for development environments though;
* so while it is important, this should have no effect in production
* environments.
*
* To be compliant with the XDG specification, we also need to strip
* certain parameters and such. Doing so properly makes parsing the
* command line unavoidable.
*
* NOTE: Technically, XDG only specifies " as quotes, while this also
* accepts '.
*/
exec_split = strv_split_full(exec, WHITESPACE, SPLIT_QUOTES | SPLIT_RELAX);
if (!exec_split)
return -ENOMEM;
if (strv_isempty(exec_split))
return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Exec line is empty");
first_arg = true;
for (i = n = 0; exec_split[i]; i++) {
_cleanup_free_ char *c = NULL, *raw = NULL, *p = NULL, *escaped = NULL, *quoted = NULL;
r = cunescape(exec_split[i], 0, &c);
if (r < 0)
return log_debug_errno(r, "Failed to unescape '%s': %m", exec_split[i]);
if (first_arg) {
_cleanup_free_ char *executable = NULL;
/* This is the executable, find it in $PATH */
first_arg = false;
r = find_binary(c, &executable);
if (r < 0)
return log_info_errno(r, "Exec binary '%s' does not exist: %m", c);
escaped = cescape(executable);
if (!escaped)
return log_oom();
free(exec_split[n]);
exec_split[n++] = TAKE_PTR(escaped);
continue;
}
/*
* Remove any standardised XDG fields; we assume they never appear as
* part of another argument as that just does not make any sense as
* they can be empty (GLib will e.g. turn "%f" into an empty argument).
* Other implementations may handle this differently.
*/
if (STR_IN_SET(c,
"%f", "%F",
"%u", "%U",
"%d", "%D",
"%n", "%N",
"%i", /* Location of icon, could be implemented. */
"%c", /* Translated application name, could be implemented. */
"%k", /* Location of desktop file, could be implemented. */
"%v",
"%m"
))
continue;
/*
* %% -> % and then % -> %% means that we correctly quote any %
* and also quote any left over (and invalid) % specifier from
* the desktop file.
*/
raw = strreplace(c, "%%", "%");
if (!raw)
return log_oom();
p = strreplace(raw, "%", "%%");
if (!p)
return log_oom();
escaped = cescape(p);
if (!escaped)
return log_oom();
quoted = strjoin("\"", escaped, "\"");
if (!quoted)
return log_oom();
free(exec_split[n]);
exec_split[n++] = TAKE_PTR(quoted);
}
for (; exec_split[n]; n++)
exec_split[n] = mfree(exec_split[n]);
res = strv_join(exec_split, " ");
if (!res)
return log_oom();
*ret_exec_start = res;
return 0;
}
static int xdg_autostart_generate_desktop_condition(
FILE *f,
const char *test_binary,
const char *condition) {
int r;
/* Generate an ExecCondition for GNOME autostart condition */
if (!isempty(condition)) {
_cleanup_free_ char *gnome_autostart_condition_path = NULL, *e_autostart_condition = NULL;
r = find_binary(test_binary, &gnome_autostart_condition_path);
if (r < 0) {
log_full_errno(r == -ENOENT ? LOG_INFO : LOG_WARNING, r,
"%s not found: %m", test_binary);
fprintf(f, "# ExecCondition using %s skipped due to missing binary.\n", test_binary);
return r;
}
e_autostart_condition = cescape(condition);
if (!e_autostart_condition)
return log_oom();
fprintf(f,
"ExecCondition=%s --condition \"%s\"\n",
gnome_autostart_condition_path,
e_autostart_condition);
}
return 0;
}
int xdg_autostart_service_generate_unit(
XdgAutostartService *service,
const char *dest) {
_cleanup_free_ char *path_escaped = NULL, *exec_start = NULL, *unit = NULL;
_cleanup_fclose_ FILE *f = NULL;
int r;
assert(service);
/* Nothing to do for hidden services. */
if (service->hidden) {
log_info("Not generating service for XDG autostart %s, it is hidden.", service->name);
return 0;
}
if (service->systemd_skip) {
log_info("Not generating service for XDG autostart %s, should be skipped by generator.", service->name);
return 0;
}
/* Nothing to do if type is not Application. */
if (!streq_ptr(service->type, "Application")) {
log_info("Not generating service for XDG autostart %s, it is hidden.", service->name);
return 0;
}
if (!service->exec_string) {
log_warning("Not generating service for XDG autostart %s, it is has no Exec= line.", service->name);
return 0;
}
/*
* The TryExec key cannot be checked properly from the systemd unit,
* it is trivial to check using find_binary though.
*/
if (service->try_exec) {
r = find_binary(service->try_exec, NULL);
if (r < 0) {
log_full_errno(r == -ENOENT ? LOG_INFO : LOG_WARNING, r,
"Not generating service for XDG autostart %s, could not find TryExec= binary %s: %m",
service->name, service->try_exec);
return 0;
}
}
r = xdg_autostart_format_exec_start(service->exec_string, &exec_start);
if (r < 0) {
log_warning_errno(r,
"Not generating service for XDG autostart %s, error parsing Exec= line: %m",
service->name);
return 0;
}
if (streq_ptr(service->gnome_autostart_phase, "EarlyInitialization")) {
log_info("Not generating service for XDG autostart %s, EarlyInitialization needs to be handled separately.",
service->name);
return 0;
}
path_escaped = specifier_escape(service->path);
if (!path_escaped)
return log_oom();
unit = path_join(dest, service->name);
if (!unit)
return log_oom();
f = fopen(unit, "wxe");
if (!f)
return log_error_errno(errno, "Failed to create unit file %s: %m", unit);
fprintf(f,
"# Automatically generated by systemd-xdg-autostart-generator\n\n"
"[Unit]\n"
"Documentation=man:systemd-xdg-autostart-generator(8)\n"
"SourcePath=%s\n"
"PartOf=graphical-session.target\n\n",
path_escaped);
if (service->description) {
_cleanup_free_ char *t = NULL;
t = specifier_escape(service->description);
if (!t)
return log_oom();
fprintf(f, "Description=%s\n", t);
}
/* Only start after the session is ready.
* XXX: GNOME has an autostart order which we may want to support.
* It is not clear how this can be implemented reliably, which
* is why it is skipped for now. */
fprintf(f,
"After=graphical-session.target\n");
fprintf(f,
"\n[Service]\n"
"Type=simple\n"
"ExecStart=:%s\n"
"Restart=no\n"
"TimeoutSec=5s\n"
"Slice=app.slice\n",
exec_start);
/* Generate an ExecCondition to check $XDG_CURRENT_DESKTOP */
if (!strv_isempty(service->only_show_in) || !strv_isempty(service->not_show_in)) {
_cleanup_free_ char *only_show_in = NULL, *not_show_in = NULL, *e_only_show_in = NULL, *e_not_show_in = NULL;
only_show_in = strv_join(service->only_show_in, ":");
not_show_in = strv_join(service->not_show_in, ":");
if (!only_show_in || !not_show_in)
return log_oom();
e_only_show_in = cescape(only_show_in);
e_not_show_in = cescape(not_show_in);
if (!e_only_show_in || !e_not_show_in)
return log_oom();
/* Just assume the values are reasonably sane */
fprintf(f,
"ExecCondition=" ROOTLIBEXECDIR "/systemd-xdg-autostart-condition \"%s\" \"%s\"\n",
e_only_show_in,
e_not_show_in);
}
r = xdg_autostart_generate_desktop_condition(f,
"gnome-systemd-autostart-condition",
service->autostart_condition);
if (r < 0)
return r;
r = xdg_autostart_generate_desktop_condition(f,
"kde-systemd-start-condition",
service->kde_autostart_condition);
if (r < 0)
return r;
(void) generator_add_symlink(dest, "xdg-desktop-autostart.target", "wants", service->name);
return 0;
}

View File

@ -0,0 +1,36 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
#include "macro.h"
typedef struct XdgAutostartService {
char *name;
char *path;
char *description; /* Name in XDG desktop file */
char *type; /* Purely as an assertion check */
char *exec_string;
char **only_show_in;
char **not_show_in;
char *try_exec;
char *autostart_condition; /* This is mostly GNOME specific */
char *kde_autostart_condition;
char *gnome_autostart_phase;
bool hidden;
bool systemd_skip;
} XdgAutostartService;
XdgAutostartService * xdg_autostart_service_free(XdgAutostartService *s);
DEFINE_TRIVIAL_CLEANUP_FUNC(XdgAutostartService*, xdg_autostart_service_free);
char *xdg_autostart_service_translate_name(const char *name);
int xdg_autostart_format_exec_start(const char *exec, char **ret_exec_start);
XdgAutostartService *xdg_autostart_service_parse_desktop(const char *path);
int xdg_autostart_service_generate_unit(XdgAutostartService *service, const char *dest);

View File

@ -186,6 +186,7 @@ NTP=
DHCP= DHCP=
Domains= Domains=
IPv6PrefixDelegation= IPv6PrefixDelegation=
IPv6PDSubnetId=
VLAN= VLAN=
DHCPServer= DHCPServer=
BindCarrier= BindCarrier=

View File

@ -0,0 +1,10 @@
[Desktop Entry]
Type=Application
Name=GNOME Settings Daemon's power plugin
Exec=/usr/bin/sleep %i %f "%F" "--test" ";\\\\!?"
OnlyShowIn=GNOME;
NoDisplay=true
X-GNOME-Autostart-Phase=Initialization
X-GNOME-Autostart-Notify=true
X-GNOME-AutoRestart=true
X-GNOME-HiddenUnderSystemd=true

View File

@ -0,0 +1,10 @@
[Desktop Entry]
Type=Application
Name=GNOME Settings Daemon's power plugin
Exec=/usr/libexec/gsd-power
OnlyShowIn=GNOME;
NoDisplay=true
X-GNOME-Autostart-Phase=Initialization
X-GNOME-Autostart-Notify=true
X-GNOME-AutoRestart=true
X-GNOME-HiddenUnderSystemd=true

View File

@ -0,0 +1,12 @@
Desktop Entry
Name=
Exec=
TryExec=
Type=
OnlyShowIn=
NotShowIn=
Hidden=
AutostartCondition=
X-KDE-autostart-condition=
X-GNOME-Autostart-Phase=
X-GNOME-HiddenUnderSystemd=

View File

@ -20,6 +20,10 @@ units = [
'timers.target', 'timers.target',
] ]
if conf.get('ENABLE_XDG_AUTOSTART') == 1
units += [ 'xdg-desktop-autostart.target', ]
endif
foreach file : units foreach file : units
install_data(file, install_data(file,
install_dir : userunitdir) install_dir : userunitdir)

View File

@ -0,0 +1,14 @@
# SPDX-License-Identifier: LGPL-2.1+
#
# 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.
[Unit]
Description=Startup of XDG autostart applications
Documentation=man:systemd.special(7)
RefuseManualStart=yes
StopWhenUnneeded=yes