[submodule "pkg/fedora"]
path = pkg/fedora
- url = https://src.fedoraproject.org/rpms/systemd
+ url = https://src.fedoraproject.org/rpms/systemd.git
branch = rawhide
[submodule "pkg/opensuse"]
path = pkg/opensuse
- url = https://src.opensuse.org/rpm/systemd
+ url = https://src.opensuse.org/rpm/systemd.git
branch = factory
[submodule "pkg/debian"]
path = pkg/debian
branch = debian/master
[submodule "pkg/centos"]
path = pkg/centos
- url = https://git.centos.org/rpms/systemd
+ url = https://git.centos.org/rpms/systemd.git
branch = c9s-sig-hyperscale
[submodule "pkg/arch"]
path = pkg/arch
- url = https://gitlab.archlinux.org/daandemeyer/systemd.git
- branch = strip
+ url = https://gitlab.archlinux.org/archlinux/packaging/packages/systemd.git
+ branch = main
- fingerprint authentication, pattern authentication, …
- make sure "classic" user records can also be managed by homed
- make size of $XDG_RUNTIME_DIR configurable in user record
- - query password from kernel keyring first
- - update even if record is "absent"
- move acct mgmt stuff from pam_systemd_home to pam_systemd?
- when "homectl --pkcs11-token-uri=" is used, synthesize ssh-authorized-keys records for all keys we have private keys on the stick for
- make slice for users configurable (requires logind rework)
EVDEV_ABS_00=::200
EVDEV_ABS_01=::200
+# HUION Kamvas 19 Pro (GT-1902)
+evdev:input:b0003v256Cp006Be0110*
+ EVDEV_ABS_00=::68
+ EVDEV_ABS_01=::123
+
#########################################
# Lenovo
#########################################
KEYBOARD_KEY_13f=f21 # Touchpad toggle
KEYBOARD_KEY_9e=wlan
+# Portege Z830 ACPI quickstart buttons
+evdev:name:Quickstart Button 1:dmi:bvn*:bvr*:bd*:svnTOSHIBA*:pnPORTEGEZ830:*
+ KEYBOARD_KEY_1=prog1 # TOSHIBA eco button
+
+evdev:name:Quickstart Button 2:dmi:bvn*:bvr*:bd*:svnTOSHIBA*:pnPORTEGEZ830:*
+ KEYBOARD_KEY_1=prog2 # TOSHIBA Presentation button
+
+evdev:name:Quickstart Button 3:dmi:bvn*:bvr*:bd*:svnTOSHIBA*:pnPORTEGEZ830:*
+ KEYBOARD_KEY_1=f21 # Touchpad toggle
+
###########################################################
# VIA
###########################################################
<term><varname>$SYSTEMD_LOG_LEVEL</varname></term>
<listitem><para id='log-level-body'>The maximum log level of emitted messages (messages with a higher
- log level, i.e. less important ones, will be suppressed). Either one of (in order of decreasing
- importance) <constant>emerg</constant>, <constant>alert</constant>, <constant>crit</constant>,
- <constant>err</constant>, <constant>warning</constant>, <constant>notice</constant>,
- <constant>info</constant>, <constant>debug</constant>, or an integer in the range 0…7. See
+ log level, i.e. less important ones, will be suppressed). Takes a comma-separated list of values. A
+ value may be either one of (in order of decreasing importance) <constant>emerg</constant>,
+ <constant>alert</constant>, <constant>crit</constant>, <constant>err</constant>,
+ <constant>warning</constant>, <constant>notice</constant>, <constant>info</constant>,
+ <constant>debug</constant>, or an integer in the range 0…7. See
<citerefentry project='man-pages'><refentrytitle>syslog</refentrytitle><manvolnum>3</manvolnum></citerefentry>
- for more information.</para>
- </listitem>
+ for more information. Each value may optionally be prefixed with one of <constant>console</constant>,
+ <constant>syslog</constant>, <constant>kmsg</constant> or <constant>journal</constant> followed by a
+ colon to set the maximum log level for that specific log target (e.g.
+ <constant>SYSTEMD_LOG_LEVEL=debug,console:info</constant> specifies to log at debug level except when
+ logging to the console which should be at info level). Note that the global maximum log level takes
+ priority over any per target maximum log levels.</para></listitem>
</varlistentry>
<varlistentry id='log-color'>
<xi:include href="version-info.xml" xpointer="v245"/></listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--offline</option></term>
+
+ <listitem><para>Do not attempt to update the copy of the user record and blob directory that is embedded inside
+ of the home area. This allows for operation on home areas that are absent, or without needing to authenticate as
+ the user being modified.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/></listitem>
+ </varlistentry>
+
<xi:include href="user-system-options.xml" xpointer="host" />
<xi:include href="user-system-options.xml" xpointer="machine" />
interface.</para>
<para><function>UpdateHome()</function> updates a locally registered user record. Takes a fully
- specified JSON user record as argument (including the <literal>secret</literal> section). A user with a
- matching name and realm must be registered locally already, and the last change timestamp of the newly
- supplied record must be newer than the previously existing user record. Note this operation updates the
- user record only, it does not propagate passwords/authentication tokens from the user record to the
+ specified JSON user record as argument (possibly including the <literal>secret</literal> section). A user
+ with a matching name and realm must be registered locally already, and the last change timestamp of the
+ newly supplied record must be newer than the previously existing user record. Note this operation updates
+ the user record only, it does not propagate passwords/authentication tokens from the user record to the
storage back-end, or resizes the storage back-end. Typically a home directory is first updated, and then
the password of the underlying storage updated using <function>ChangePasswordHome()</function> as well
as the storage resized using <function>ResizeHome()</function>. This method is equivalent to
Directories</ulink> for more info). The <varname>blobs</varname> argument works in the same way as
<function>CreateHomeEx()</function>, so check there for details. The new blob directory contents passed into
this method will completely replace the user's existing blob directory. The <varname>flags</varname> argument
- may be used for future expansion, but for now pass 0. This method is equivalent to <function>UpdateEx()</function>
- on the <classname>org.freedesktop.home1.Home</classname> interface.</para>
+ can be used to further customize the behavior of this method via flags defined as follows:</para>
+ <programlisting>
+#define SD_HOMED_UPDATE_OFFLINE (UINT64_C(1) << 0)
+ </programlisting>
+ <para>When <constant>SD_HOMED_UPDATE_OFFLINE</constant> (0x01) is set, no attempt is made to update the copies
+ of the user record and blob directory that are embedded into the home directory. Changes will be stored, however,
+ and may be propagated into the home directory the next time it is reconciled (most likely when the user next logs in).
+ Note that any changes made with this flag set may be lost if the home area has a newer record, which can happen
+ if the home area is updated on another machine after this method call. This method is equivalent to
+ <function>UpdateEx()</function> on the <classname>org.freedesktop.home1.Home</classname> interface.</para>
<para><function>ResizeHome()</function> resizes the storage associated with a user record. Takes a user
- name, a disk size in bytes and a user record consisting only of the <literal>secret</literal> section
- as argument. If the size is specified as <constant>UINT64_MAX</constant> the storage is resized to the
- size already specified in the user record. Typically, if the user record is updated using
+ name, a disk size in bytes, and optionally a user record consisting only of the <literal>secret</literal>
+ section as arguments. If the size is specified as <constant>UINT64_MAX</constant> the storage is resized to
+ the size already specified in the user record. Typically, if the user record is updated using
<function>UpdateHome()</function> above this is used to propagate the size configured there-in down to
- the underlying storage back-end. This method is equivalent to
- <function>Resize()</function> on the <classname>org.freedesktop.home1.Home</classname>
- interface.</para>
+ the underlying storage back-end. This method is equivalent to <function>Resize()</function> on the
+ <classname>org.freedesktop.home1.Home</classname> interface.</para>
<para><function>ChangePasswordHome()</function> changes the passwords/authentication tokens of a home
directory. Takes a user name, and two JSON user record objects, each consisting only of the
<term><option>--order</option></term>
<term><option>--require</option></term>
- <listitem><para>When used in conjunction with the
- <command>dot</command> command (see above), selects which
- dependencies are shown in the dependency graph. If
- <option>--order</option> is passed, only dependencies of type
- <varname>After=</varname> or <varname>Before=</varname> are
- shown. If <option>--require</option> is passed, only
- dependencies of type <varname>Requires=</varname>,
- <varname>Requisite=</varname>,
- <varname>Wants=</varname> and <varname>Conflicts=</varname>
- are shown. If neither is passed, this shows dependencies of
+ <listitem><para>When used in conjunction with the <command>dot</command> command (see above),
+ selects which dependencies are shown in the dependency graph. If <option>--order</option> is passed,
+ only dependencies of type <varname>After=</varname> or <varname>Before=</varname> are shown.
+ If <option>--require</option> is passed, only dependencies of type <varname>Requires=</varname>,
+ <varname>Requisite=</varname>, <varname>BindsTo=</varname>, <varname>Wants=</varname>, and
+ <varname>Conflicts=</varname> are shown. If neither is passed, this shows dependencies of
all these types.</para>
<xi:include href="version-info.xml" xpointer="v198"/></listitem>
<xi:include href="version-info.xml" xpointer="v256"/>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><option>--pass-ssh-key=</option><replaceable>BOOL</replaceable></term>
+
+ <listitem><para>By default an SSH key is generated to allow <command>systemd-vmspawn</command> to open
+ a D-Bus connection to the VM's systemd bus. Setting this to "no" will disable SSH key generation.</para>
+
+ <para>The generated keys are ephemeral. That is they are valid only for the current invocation of <command>systemd-vmspawn</command>,
+ and are typically not persisted.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--ssh-key-type=</option><replaceable>TYPE</replaceable></term>
+
+ <listitem><para>Configures the type of SSH key to generate, see
+ <citerefentry><refentrytitle>ssh-keygen</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ for more information.</para>
+
+ <para>By default <literal>ed25519</literal> keys are generated, however <literal>rsa</literal> keys
+ may also be useful if the VM has a particularly old version of <command>sshd</command></para>.
+
+ <xi:include href="version-info.xml" xpointer="v256"/>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect2>
'crypt_reencrypt_init_by_passphrase',
'crypt_reencrypt',
'crypt_set_data_offset',
- 'crypt_set_keyring_to_link']
+ 'crypt_set_keyring_to_link',
+ 'crypt_resume_by_volume_key']
have_ident = have and cc.has_function(
ident,
prefix : '#include <libcryptsetup.h>',
have = get_option('homed').require(
conf.get('HAVE_OPENSSL') == 1 and
conf.get('HAVE_LIBFDISK') == 1 and
- conf.get('HAVE_LIBCRYPTSETUP') == 1,
+ conf.get('HAVE_LIBCRYPTSETUP') == 1 and
+ conf.get('HAVE_CRYPT_RESUME_BY_VOLUME_KEY') == 1,
error_message : 'openssl, fdisk and libcryptsetup required').allowed()
conf.set10('ENABLE_HOMED', have)
'-std=gnu11',
'-fno-stack-protector',
'-O2',
- '-mkernel=5.2',
'-mcpu=v3',
'-mco-re',
'-gbtf',
bpf_o_unstripped_cmd += ['-I.']
- if not meson.is_cross_build() and bpf_compiler == 'clang'
+ if not meson.is_cross_build()
target_triplet_cmd = run_command('gcc', '-dumpmachine', check: false)
if target_triplet_cmd.returncode() == 0
target_triplet = target_triplet_cmd.stdout().strip()
#####################################################################
+check_efi_alignment_py = find_program('tools/check-efi-alignment.py')
check_version_history_py = find_program('tools/check-version-history.py')
elf2efi_py = find_program('tools/elf2efi.py')
export_dbus_interfaces_py = find_program('tools/dbus_exporter.py')
enforcing=0
systemd.early_core_pattern=/core
systemd.firstboot=no
+ systemd.setenv=SYSTEMD_ENABLE_LOG_CONTEXT=yes
+ SYSTEMD_ENABLE_LOG_CONTEXT=yes
kexec-tools
kmod
less
+ man
mtools
nano
nftables
# tmpfs during the build script so these changes don't end up in the image itself.
tee --append /etc/makepkg.conf >/dev/null <<EOF
CFLAGS="$CFLAGS -Og"
-OPTIONS=(!strip docs !libtool !staticlibs emptydirs !zipman purge debug !lto)
+OPTIONS=(!strip docs !libtool !staticlibs emptydirs !zipman purge !debug !lto)
EOF
# Linting the PKGBUILD takes multiple seconds every build so avoid that by nuking all the linting functions.
TS="${SOURCE_DATE_EPOCH:-$(date +%s)}"
fi
+sed --in-place "pkg/$PKG_SUBDIR/PKGBUILD" \
+ --expression "s/^_tag=.*/_tag=$(cat meson.version)/" \
+ --expression "s/^pkgrel=.*/pkgrel=$(date "+%Y%m%d%H%M%S" --date "@$TS")/"
+
# We get around makepkg's root check by setting EUID to something else.
# shellcheck disable=SC2046
env --chdir="pkg/$PKG_SUBDIR" \
--noextract \
$( ((WITH_TESTS)) || echo --nocheck) \
--force \
- UPSTREAM=1 \
- QUIET=1 \
+ _systemd_UPSTREAM=1 \
+ _systemd_QUIET=1 \
BUILDDIR="$PWD/pkg/$PKG_SUBDIR" \
PKGDEST="$PACKAGEDIR" \
PKGEXT=".pkg.tar" \
- PKGVER="$(cat meson.version)" \
- PKGREL="$(date "+%Y%m%d%H%M%S" --date "@$TS")" \
MESON_EXTRA_CONFIGURE_OPTIONS="-D mode=developer -D b_sanitize=${SANITIZERS:-none}"
libasan
libcap-ng-utils
libubsan
+ man-db
netcat
openssh-clients
openssh-server
vim-common
InitrdPackages=
+ setools
+ selinux-policy
tpm2-tools
libcap-ng-utils
libtss2-rc0
libtss2-tcti-device0
+ man-db
netcat-openbsd
openssh-client
openssh-server
mountpoint -q /etc/resolv.conf && umount /etc/resolv.conf
rm -f /etc/resolv.conf
-. /usr/lib/os-release
-
-mkdir -p /usr/lib/sysusers.d
-cat >/usr/lib/sysusers.d/testuser.conf <<EOF
-u testuser 4711 "Test User" /home/testuser
-EOF
-mkdir -p /usr/lib/tmpfiles.d
-cat >/usr/lib/tmpfiles.d/testuser.conf <<EOF
-q /home/testuser 0700 4711 4711
-EOF
-
# sbsign is not available on CentOS Stream
if command -v sbsign &>/dev/null; then
# Ensure that side-loaded PE addons are loaded if signed, and ignored if not
--- /dev/null
+u testuser 4711 "Test User" /home/testuser
--- /dev/null
+q /home/testuser 0700 4711 4711
-Subproject commit b33762d07ce712f5c675bdc8f882a096aa59bf09
+Subproject commit 3b86b9146b84d499789ba924a9dd4ac643d796ab
-Subproject commit 3b4728136724b960f453de26e59ee39061575a8f
+Subproject commit 1932e19d92daef5928a1402073ad3b5aa6fc0767
-Subproject commit 2e32a339a10caad9392a7049bccfd1c4cd7c24cc
+Subproject commit f1d38667ef013aa832f43ea7b5861efd29b09fee
# Julien Humbert <julroy67@gmail.com>, 2020, 2021.
# Arnaud T. <listes.00@gmail.com>, 2021.
# blutch112 <vincent.lefebvre59@gmail.com>, 2022.
-# Pierre GRASSER <pierre.grasser@proton.me>, 2023.
+# Pierre GRASSER <pierre.grasser@proton.me>, 2023, 2024.
msgid ""
msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-04 10:09+0100\n"
-"PO-Revision-Date: 2023-10-12 16:36+0000\n"
+"PO-Revision-Date: 2024-03-22 10:36+0000\n"
"Last-Translator: Pierre GRASSER <pierre.grasser@proton.me>\n"
"Language-Team: French <https://translate.fedoraproject.org/projects/systemd/"
-"master/fr/>\n"
+"main/fr/>\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
-"X-Generator: Weblate 5.0.2\n"
+"X-Generator: Weblate 5.4\n"
#: src/core/org.freedesktop.systemd1.policy.in:22
msgid "Send passphrase back to system"
#: src/home/org.freedesktop.home1.policy:73
msgid "Inhibit automatic lock of a home area"
-msgstr ""
+msgstr "Empêcher le verrouillage automatique d'un espace personnel"
#: src/home/org.freedesktop.home1.policy:74
-#, fuzzy
msgid ""
"Authentication is required to inhibit automatic lock of a user's home area."
msgstr ""
-"Authentification requise pour mettre à jour l’espace personnel d’un "
-"utilisateur."
+"Authentification requise pour empêcher le verrouillage automatique de "
+"l'espace personnel d'un utilisateur."
#: src/home/org.freedesktop.home1.policy:83
-#, fuzzy
msgid "Activate a home area"
-msgstr "Créer un espace personnel"
+msgstr "Activer un espace personnel"
#: src/home/org.freedesktop.home1.policy:84
-#, fuzzy
msgid "Authentication is required to activate a user's home area."
msgstr ""
-"Authentification requise pour créer l’espace personnel d’un utilisateur."
+"Authentification requise pour activer l’espace personnel d’un utilisateur."
#: src/home/pam_systemd_home.c:293
#, c-format
msgstr "Authentification requise pour obtenir la description du système."
#: src/import/org.freedesktop.import1.policy:22
-#, fuzzy
msgid "Import a disk image"
-msgstr "Importer une image de machine virtuelle ou de conteneur"
+msgstr "Importer une image disque"
#: src/import/org.freedesktop.import1.policy:23
-#, fuzzy
msgid "Authentication is required to import an image"
-msgstr ""
-"Authentification requise pour importer une image de machine virtuelle ou de "
-"conteneur"
+msgstr "Authentification requise pour importer une image"
#: src/import/org.freedesktop.import1.policy:32
-#, fuzzy
msgid "Export a disk image"
-msgstr "Exporter une image de machine virtuelle ou de conteneur"
+msgstr "Exporter une image disque"
#: src/import/org.freedesktop.import1.policy:33
-#, fuzzy
msgid "Authentication is required to export disk image"
-msgstr ""
-"Authentification requise pour exporter une image de machine virtuelle ou de "
-"conteneur"
+msgstr "Authentification requise pour exporter une image disque."
#: src/import/org.freedesktop.import1.policy:42
-#, fuzzy
msgid "Download a disk image"
-msgstr "Télécharger une image de machine virtuelle (VM) ou de conteneur"
+msgstr "Télécharger une image disque"
#: src/import/org.freedesktop.import1.policy:43
-#, fuzzy
msgid "Authentication is required to download a disk image"
-msgstr ""
-"Authentification requise pour télécharger une image de machine virtuelle ou "
-"de conteneur"
+msgstr "Authentification requise pour télécharger une image disque."
#: src/import/org.freedesktop.import1.policy:52
msgid "Cancel transfer of a disk image"
-msgstr ""
+msgstr "Annuler le transfert d'une image disque"
#: src/import/org.freedesktop.import1.policy:53
-#, fuzzy
msgid ""
"Authentication is required to cancel the ongoing transfer of a disk image"
msgstr ""
-"Authentification requise pour changer le mot de passe de l’espace personnel "
-"d’un utilisateur."
+"Authentification requise pour annuler le transfert en cours d'une image "
+"disque."
#: src/locale/org.freedesktop.locale1.policy:22
msgid "Set system locale"
const UnitInfo *u,
const char *prop,
const char *color,
- char *patterns[],
- char *from_patterns[],
- char *to_patterns[]) {
+ char **patterns,
+ char **from_patterns,
+ char **to_patterns) {
_cleanup_strv_free_ char **units = NULL;
bool match_patterns;
int r;
+ assert(bus);
assert(u);
assert(prop);
assert(color);
return 0;
}
-static int graph_one(sd_bus *bus, const UnitInfo *u, char *patterns[], char *from_patterns[], char *to_patterns[]) {
+static int graph_one(
+ sd_bus *bus,
+ const UnitInfo *u,
+ char **patterns,
+ char **from_patterns,
+ char **to_patterns) {
+
int r;
assert(bus);
r = graph_one_property(bus, u, "Requires", "black", patterns, from_patterns, to_patterns);
if (r < 0)
return r;
+
r = graph_one_property(bus, u, "Requisite", "darkblue", patterns, from_patterns, to_patterns);
if (r < 0)
return r;
+
+ r = graph_one_property(bus, u, "BindsTo", "gold", patterns, from_patterns, to_patterns);
+ if (r < 0)
+ return r;
+
r = graph_one_property(bus, u, "Wants", "grey66", patterns, from_patterns, to_patterns);
if (r < 0)
return r;
+
r = graph_one_property(bus, u, "Conflicts", "red", patterns, from_patterns, to_patterns);
if (r < 0)
return r;
_cleanup_strv_free_ char **expanded_patterns = NULL;
int r;
+ assert(bus);
+ assert(ret);
+
STRV_FOREACH(pattern, patterns) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_free_ char *unit = NULL, *unit_id = NULL;
if (r < 0)
return log_error_errno(r, "Failed to get ID: %s", bus_error_message(&error, r));
- if (!streq(*pattern, unit_id)) {
+ if (!streq(*pattern, unit_id))
if (strv_extend(&expanded_patterns, unit_id) < 0)
return log_oom();
- }
}
*ret = TAKE_PTR(expanded_patterns); /* do not free */
_cleanup_strv_free_ char **expanded_patterns = NULL;
_cleanup_strv_free_ char **expanded_from_patterns = NULL;
_cleanup_strv_free_ char **expanded_to_patterns = NULL;
- int r;
UnitInfo u;
+ int r;
r = acquire_bus(&bus, NULL);
if (r < 0)
log_info(" Color legend: black = Requires\n"
" dark blue = Requisite\n"
+ " gold = BindsTo\n"
" dark grey = Wants\n"
" red = Conflicts\n"
" green = After\n");
(void) table_set_display(details_table, (size_t) 0, (size_t) 1, (size_t) 2, (size_t) 3, (size_t) 7);
}
- for (i = 0; i < ELEMENTSOF(security_assessor_table); i++) {
- const struct security_assessor *a = security_assessor_table + i;
+ FOREACH_ARRAY(a, security_assessor_table, ELEMENTSOF(security_assessor_table)) {
_cleanup_free_ char *d = NULL;
uint64_t badness;
void *data;
uint64_t weight = access_weight(a, policy);
uint64_t range = access_range(a, policy);
- data = (uint8_t *) info + a->offset;
+ data = (uint8_t*) info + a->offset;
if (a->default_dependencies_only && !info->default_dependencies) {
badness = UINT64_MAX;
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Option --threshold= is only supported for security right now.");
- if (arg_runtime_scope == RUNTIME_SCOPE_GLOBAL &&
- !STR_IN_SET(argv[optind] ?: "time", "dot", "unit-paths", "verify"))
+ if (arg_runtime_scope == RUNTIME_SCOPE_GLOBAL && !streq_ptr(argv[optind], "unit-paths"))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "Option --global only makes sense with verbs dot, unit-paths, verify.");
+ "Option --global only makes sense with verb unit-paths.");
if (streq_ptr(argv[optind], "cat-config") && arg_runtime_scope == RUNTIME_SCOPE_USER)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
static LogTarget log_target = LOG_TARGET_CONSOLE;
static int log_max_level = LOG_INFO;
+static int log_target_max_level[] = {
+ [LOG_TARGET_CONSOLE] = INT_MAX,
+ [LOG_TARGET_KMSG] = INT_MAX,
+ [LOG_TARGET_SYSLOG] = INT_MAX,
+ [LOG_TARGET_JOURNAL] = INT_MAX,
+};
static int log_facility = LOG_DAEMON;
static bool ratelimit_kmsg = true;
if (console_fd < 0)
return 0;
+ if (LOG_PRI(level) > log_target_max_level[LOG_TARGET_CONSOLE])
+ return 0;
+
if (log_target == LOG_TARGET_CONSOLE_PREFIXED) {
xsprintf(prefix, "<%i>", level);
iovec[n++] = IOVEC_MAKE_STRING(prefix);
if (syslog_fd < 0)
return 0;
+ if (LOG_PRI(level) > log_target_max_level[LOG_TARGET_SYSLOG])
+ return 0;
+
xsprintf(header_priority, "<%i>", level);
t = (time_t) (now(CLOCK_REALTIME) / USEC_PER_SEC);
if (kmsg_fd < 0)
return 0;
+ if (LOG_PRI(level) > log_target_max_level[LOG_TARGET_KMSG])
+ return 0;
+
if (ratelimit_kmsg && !ratelimit_below(&ratelimit)) {
if (ratelimit_num_dropped(&ratelimit) > 1)
return 0;
if (journal_fd < 0)
return 0;
+ if (LOG_PRI(level) > log_target_max_level[LOG_TARGET_JOURNAL])
+ return 0;
+
iovec_len = MIN(6 + _log_context_num_fields * 2, IOVEC_MAX);
iovec = newa(struct iovec, iovec_len);
PROTECT_ERRNO;
log_assert(LOG_DEBUG, text, file, line, func,
- "Assertion '%s' failed at %s:%u, function %s(). Ignoring.");
+ "Assertion '%s' failed at %s:%u, function %s(), ignoring.");
}
int log_oom_internal(int level, const char *file, int line, const char *func) {
int log_set_max_level_from_string(const char *e) {
int r;
- r = log_level_from_string(e);
+ for (;;) {
+ _cleanup_free_ char *word = NULL, *prefix = NULL;
+ LogTarget target;
+ const char *colon;
+
+ r = extract_first_word(&e, &word, ",", 0);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ colon = strchr(word, ':');
+ if (!colon) {
+ r = log_level_from_string(word);
+ if (r < 0)
+ return r;
+
+ log_set_max_level(r);
+ continue;
+ }
+
+ prefix = strndup(word, colon - word);
+ if (!prefix)
+ return -ENOMEM;
+
+ target = log_target_from_string(prefix);
+ if (target < 0)
+ return target;
+
+ if (target >= _LOG_TARGET_SINGLE_MAX)
+ return -EINVAL;
+
+ r = log_level_from_string(colon + 1);
+ if (r < 0)
+ return r;
+
+ log_target_max_level[target] = r;
+ }
+
+ return 0;
+}
+
+int log_max_levels_to_string(int level, char **ret) {
+ _cleanup_free_ char *s = NULL;
+ int r;
+
+ assert(ret);
+
+ r = log_level_to_string_alloc(level, &s);
if (r < 0)
return r;
- log_set_max_level(r);
+ for (LogTarget target = 0; target < _LOG_TARGET_SINGLE_MAX; target++) {
+ _cleanup_free_ char *l = NULL;
+
+ if (log_target_max_level[target] == INT_MAX)
+ continue;
+
+ r = log_level_to_string_alloc(log_target_max_level[target], &l);
+ if (r < 0)
+ return r;
+
+ r = strextendf_with_separator(&s, ",", "%s:%s", log_target_to_string(target), l);
+ if (r < 0)
+ return r;
+ }
+
+ *ret = TAKE_PTR(s);
return 0;
}
return 0;
if (log_set_target_from_string(value) < 0)
- log_warning("Failed to parse log target '%s'. Ignoring.", value);
+ log_warning("Failed to parse log target '%s', ignoring.", value);
} else if (proc_cmdline_key_streq(key, "systemd.log_level")) {
return 0;
if (log_set_max_level_from_string(value) < 0)
- log_warning("Failed to parse log level '%s'. Ignoring.", value);
+ log_warning("Failed to parse log level setting '%s', ignoring.", value);
} else if (proc_cmdline_key_streq(key, "systemd.log_color")) {
if (log_show_color_from_string(value ?: "1") < 0)
- log_warning("Failed to parse log color setting '%s'. Ignoring.", value);
+ log_warning("Failed to parse log color setting '%s', ignoring.", value);
} else if (proc_cmdline_key_streq(key, "systemd.log_location")) {
if (log_show_location_from_string(value ?: "1") < 0)
- log_warning("Failed to parse log location setting '%s'. Ignoring.", value);
+ log_warning("Failed to parse log location setting '%s', ignoring.", value);
} else if (proc_cmdline_key_streq(key, "systemd.log_tid")) {
if (log_show_tid_from_string(value ?: "1") < 0)
- log_warning("Failed to parse log tid setting '%s'. Ignoring.", value);
+ log_warning("Failed to parse log tid setting '%s', ignoring.", value);
} else if (proc_cmdline_key_streq(key, "systemd.log_time")) {
if (log_show_time_from_string(value ?: "1") < 0)
- log_warning("Failed to parse log time setting '%s'. Ignoring.", value);
+ log_warning("Failed to parse log time setting '%s', ignoring.", value);
} else if (proc_cmdline_key_streq(key, "systemd.log_ratelimit_kmsg")) {
if (log_set_ratelimit_kmsg_from_string(value ?: "1") < 0)
- log_warning("Failed to parse log ratelimit kmsg boolean '%s'. Ignoring.", value);
+ log_warning("Failed to parse log ratelimit kmsg boolean '%s', ignoring.", value);
}
return 0;
e = getenv("SYSTEMD_LOG_TARGET");
if (e && log_set_target_from_string(e) < 0)
- log_warning("Failed to parse log target '%s'. Ignoring.", e);
+ log_warning("Failed to parse log target '%s', ignoring.", e);
e = getenv("SYSTEMD_LOG_LEVEL");
if (e && log_set_max_level_from_string(e) < 0)
- log_warning("Failed to parse log level '%s'. Ignoring.", e);
+ log_warning("Failed to parse log level '%s', ignoring.", e);
e = getenv("SYSTEMD_LOG_COLOR");
if (e && log_show_color_from_string(e) < 0)
- log_warning("Failed to parse log color '%s'. Ignoring.", e);
+ log_warning("Failed to parse log color '%s', ignoring.", e);
e = getenv("SYSTEMD_LOG_LOCATION");
if (e && log_show_location_from_string(e) < 0)
- log_warning("Failed to parse log location '%s'. Ignoring.", e);
+ log_warning("Failed to parse log location '%s', ignoring.", e);
e = getenv("SYSTEMD_LOG_TIME");
if (e && log_show_time_from_string(e) < 0)
- log_warning("Failed to parse log time '%s'. Ignoring.", e);
+ log_warning("Failed to parse log time '%s', ignoring.", e);
e = getenv("SYSTEMD_LOG_TID");
if (e && log_show_tid_from_string(e) < 0)
- log_warning("Failed to parse log tid '%s'. Ignoring.", e);
+ log_warning("Failed to parse log tid '%s', ignoring.", e);
e = getenv("SYSTEMD_LOG_RATELIMIT_KMSG");
if (e && log_set_ratelimit_kmsg_from_string(e) < 0)
- log_warning("Failed to parse log ratelimit kmsg boolean '%s'. Ignoring.", e);
+ log_warning("Failed to parse log ratelimit kmsg boolean '%s', ignoring.", e);
}
void log_parse_environment(void) {
typedef enum LogTarget{
LOG_TARGET_CONSOLE,
- LOG_TARGET_CONSOLE_PREFIXED,
LOG_TARGET_KMSG,
LOG_TARGET_JOURNAL,
- LOG_TARGET_JOURNAL_OR_KMSG,
LOG_TARGET_SYSLOG,
+ LOG_TARGET_CONSOLE_PREFIXED,
+ LOG_TARGET_JOURNAL_OR_KMSG,
LOG_TARGET_SYSLOG_OR_KMSG,
LOG_TARGET_AUTO, /* console if stderr is not journal, JOURNAL_OR_KMSG otherwise */
LOG_TARGET_NULL,
- _LOG_TARGET_MAX,
+ _LOG_TARGET_SINGLE_MAX = LOG_TARGET_SYSLOG + 1,
+ _LOG_TARGET_MAX = LOG_TARGET_NULL + 1,
_LOG_TARGET_INVALID = -EINVAL,
} LogTarget;
void log_set_max_level(int level);
int log_set_max_level_from_string(const char *e);
int log_get_max_level(void) _pure_;
+int log_max_levels_to_string(int level, char **ret);
void log_set_facility(int facility);
return 0;
}
-static inline bool path_equal_ptr(const char *a, const char *b) {
- return !!a == !!b && (!a || path_equal(a, b));
-}
-
/* Note: the search terminates on the first NULL item. */
#define PATH_IN_SET(p, ...) path_strv_contains(STRV_MAKE(__VA_ARGS__), p)
* When looking under root_dir, we can't expect /dev/ to be mounted,
* so let's see if the path is a (possibly dangling) symlink to /dev/null. */
- if (path_equal_ptr(path_startswith(fn, root ?: "/"), "dev/null"))
+ if (path_equal(path_startswith(fn, root ?: "/"), "dev/null"))
return true;
r = chase_and_stat(fn, root, CHASE_PREFIX_ROOT, NULL, &st);
s = strdup(name##_table[i]); \
if (!s) \
return -ENOMEM; \
- } else { \
- if (asprintf(&s, "%i", i) < 0) \
- return -ENOMEM; \
- } \
+ } else if (asprintf(&s, "%i", i) < 0) \
+ return -ENOMEM; \
*str = s; \
return 0; \
}
{ "VMW", VIRTUALIZATION_VMWARE },
{ "innotek GmbH", VIRTUALIZATION_ORACLE },
{ "VirtualBox", VIRTUALIZATION_ORACLE },
+ { "Oracle Corporation", VIRTUALIZATION_ORACLE }, /* Detect VirtualBox on some proprietary systems via the board_vendor */
{ "Xen", VIRTUALIZATION_XEN },
{ "Bochs", VIRTUALIZATION_BOCHS },
{ "Parallels", VIRTUALIZATION_PARALLELS },
efi_arch_c_args = {
'aarch64' : ['-mgeneral-regs-only'],
'arm' : ['-mgeneral-regs-only'],
- # Until -mgeneral-regs-only is supported in LoongArch, use the following option instead:
+ # Until -mgeneral-regs-only is supported in LoongArch, use the following option instead:
'loongarch64' : ['-mno-lsx', '-mno-lasx'],
# Pass -m64/32 explicitly to make building on x32 work.
'x86_64' : ['-m64', '-march=x86-64', '-mno-red-zone', '-mgeneral-regs-only'],
if name == 'addon@0@.efi.stub'.format(efi_arch)
efi_addon = exe.full_path()
endif
+
+ test('check-alignment-@0@'.format(name),
+ check_efi_alignment_py,
+ args : exe.full_path(),
+ suite : 'efi')
endforeach
alias_target('systemd-boot', boot_targets)
assert(map_fd >= 0);
+ if (!head) {
+ static const struct socket_bind_rule val = {
+ .address_family = SOCKET_BIND_RULE_AF_MATCH_NOTHING,
+ };
+
+ if (sym_bpf_map_update_elem(map_fd, &i, &val, BPF_ANY) != 0)
+ return -errno;
+ }
+
LIST_FOREACH(socket_bind_items, item, head) {
struct socket_bind_rule val = {
.address_family = (uint32_t) item->address_family,
*/
#include <linux/types.h>
+#include <stdint.h>
/*
* Bind rule is matched with socket fields accessible to cgroup/bind{4,6} hook
* through bpf_sock_addr struct.
- * 'address_family' is expected to be one of AF_UNSPEC, AF_INET or AF_INET6.
+ * 'address_family' is expected to be one of AF_UNSPEC, AF_INET, AF_INET6 or the
+ * magic SOCKET_BIND_RULE_AF_MATCH_NOTHING.
* Matching by family is bypassed for rules with AF_UNSPEC set, which makes the
* rest of a rule applicable for both IPv4 and IPv6 addresses.
+ * If SOCKET_BIND_RULE_AF_MATCH_NOTHING is set the rule fails unconditionally
+ * and other checks are skipped.
* If matching by family is either successful or bypassed, a rule and a socket
* are matched by ip protocol.
* If 'protocol' is 0, matching is bypassed.
};
#define SOCKET_BIND_MAX_RULES 128
+#define SOCKET_BIND_RULE_AF_MATCH_NOTHING UINT32_MAX
__u32 protocol,
__u16 port,
const struct socket_bind_rule *r) {
+ if (r->address_family == SOCKET_BIND_RULE_AF_MATCH_NOTHING)
+ return false;
+
return match_af(address_family, r) &&
match_protocol(protocol, r) &&
match_user_port(port, r);
}
static int make_uid_symlinks(uid_t uid, const char *name, bool b) {
-
char path1[STRLEN("/run/systemd/dynamic-uid/direct:") + DECIMAL_STR_MAX(uid_t) + 1];
const char *path2;
int r = 0, k;
int dynamic_creds_make(Manager *m, const char *user, const char *group, DynamicCreds **ret) {
_cleanup_(dynamic_creds_unrefp) DynamicCreds *creds = NULL;
- bool acquired = false;
int r;
assert(m);
r = dynamic_user_acquire(m, user, &creds->user);
if (r < 0)
return r;
-
- acquired = true;
}
- if (creds->user && (!group || streq_ptr(user, group)))
- creds->group = dynamic_user_ref(creds->user);
- else if (group) {
+ if (group && !streq_ptr(user, group)) {
r = dynamic_user_acquire(m, group, &creds->group);
- if (r < 0) {
- if (acquired)
- creds->user = dynamic_user_unref(creds->user);
+ if (r < 0)
return r;
- }
- }
+ } else
+ creds->group = ASSERT_PTR(dynamic_user_ref(creds->user));
*ret = TAKE_PTR(creds);
* to inherit the $TERM set for PID 1. This is useful for containers so that the $TERM the
* container manager passes to PID 1 ends up all the way in the console login shown. */
- if (path_equal_ptr(tty_path, "/dev/console") && getppid() == 1)
+ if (path_equal(tty_path, "/dev/console") && getppid() == 1)
term = getenv("TERM");
else if (tty_path && in_charset(skip_dev_prefix(tty_path), ALPHANUMERICAL)) {
_cleanup_free_ char *key = NULL;
PidRef *ret) {
char serialization_fd_number[DECIMAL_STR_MAX(int) + 1];
- _cleanup_free_ char *subcgroup_path = NULL, *log_level = NULL, *executor_path = NULL;
+ _cleanup_free_ char *subcgroup_path = NULL, *max_log_levels = NULL, *executor_path = NULL;
_cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
_cleanup_fdset_free_ FDSet *fdset = NULL;
_cleanup_fclose_ FILE *f = NULL;
/* If LogLevelMax= is specified, then let's use the specified log level at the beginning of the
* executor process. To achieve that the specified log level is passed as an argument, rather than
* the one for the manager process. */
- r = log_level_to_string_alloc(context->log_level_max >= 0 ? context->log_level_max : log_get_max_level(), &log_level);
+ r = log_max_levels_to_string(context->log_level_max >= 0 ? context->log_level_max : log_get_max_level(), &max_log_levels);
if (r < 0)
- return log_unit_error_errno(unit, r, "Failed to convert log level to string: %m");
+ return log_unit_error_errno(unit, r, "Failed to convert max log levels to string: %m");
r = fd_get_path(unit->manager->executor_fd, &executor_path);
if (r < 0)
FORMAT_PROC_FD_PATH(unit->manager->executor_fd),
STRV_MAKE(executor_path,
"--deserialize", serialization_fd_number,
- "--log-level", log_level,
+ "--log-level", max_log_levels,
"--log-target", log_target_to_string(manager_get_executor_log_target(unit->manager))),
environ,
cg_unified() > 0 ? subcgroup_path : NULL,
if (!c->user)
return true;
- if (streq(c->user, "root") || streq(c->user, "0"))
+ if (STR_IN_SET(c->user, "root", "0"))
return true;
return false;
return 0;
/* Do not try to merge initrd credentials into foreign credentials directories */
- if (!path_equal_ptr(creds_dir, SYSTEM_CREDENTIALS_DIRECTORY)) {
+ if (!path_equal(creds_dir, SYSTEM_CREDENTIALS_DIRECTORY)) {
log_debug("Not importing initrd credentials, as foreign $CREDENTIALS_DIRECTORY has been set.");
return 0;
}
/* Fragment paths should also be equal as a custom fragment for a specific template instance
* wouldn't necessarily lead to infinite recursion. */
- if (!path_equal_ptr(u->fragment_path, fragment_path))
+ if (!path_equal(u->fragment_path, fragment_path))
return false;
if (!contains_instance_specifier_superset(format))
return -errno;
/* The kernels sets HOME=/ for init. Let's undo this. */
- if (path_equal_ptr(getenv("HOME"), "/"))
+ if (path_equal(getenv("HOME"), "/"))
assert_se(unsetenv("HOME") == 0);
return 0;
[MANAGER_KEXEC] = "kexec",
};
- char log_level[STRLEN("--log-level=") + DECIMAL_STR_MAX(int)],
- timeout[STRLEN("--timeout=") + DECIMAL_STR_MAX(usec_t) + STRLEN("us")],
+ char timeout[STRLEN("--timeout=") + DECIMAL_STR_MAX(usec_t) + STRLEN("us")],
exit_code[STRLEN("--exit-code=") + DECIMAL_STR_MAX(uint8_t)];
_cleanup_strv_free_ char **env_block = NULL;
+ _cleanup_free_ char *max_log_levels = NULL;
usec_t watchdog_timer = 0;
int r;
assert(objective >= 0 && objective < _MANAGER_OBJECTIVE_MAX);
assert(table[objective]);
- xsprintf(log_level, "--log-level=%d", log_get_max_level());
xsprintf(timeout, "--timeout=%" PRI_USEC "us", arg_defaults.timeout_stop_usec);
- const char* command_line[10] = {
+ const char* command_line[11] = {
SYSTEMD_SHUTDOWN_BINARY_PATH,
table[objective],
- log_level,
timeout,
/* Note that the last position is a terminator and must contain NULL. */
};
- size_t pos = 4;
+ size_t pos = 3;
assert(command_line[pos-1]);
assert(!command_line[pos]);
+ (void) log_max_levels_to_string(log_get_max_level(), &max_log_levels);
+
+ if (max_log_levels) {
+ command_line[pos++] = "--log-level";
+ command_line[pos++] = max_log_levels;
+ }
+
switch (log_get_target()) {
case LOG_TARGET_KMSG:
Unit **ret) {
_cleanup_(unit_freep) Unit *u = NULL;
+ Mount *mnt;
int r;
assert(m);
if (r < 0)
return r;
+ mnt = ASSERT_PTR(MOUNT(u));
+
r = free_and_strdup(&u->source_path, "/proc/self/mountinfo");
if (r < 0)
return r;
- r = free_and_strdup(&MOUNT(u)->where, where);
+ r = free_and_strdup(&mnt->where, where);
if (r < 0)
return r;
- r = update_parameters_proc_self_mountinfo(MOUNT(u), what, options, fstype);
+ r = update_parameters_proc_self_mountinfo(mnt, what, options, fstype);
if (r < 0)
return r;
/* This unit was generated because /proc/self/mountinfo reported it. Remember this, so that by the
* time we load the unit file for it (and thus add in extra deps right after) we know what source to
* attributes the deps to. */
- MOUNT(u)->from_proc_self_mountinfo = true;
+ mnt->from_proc_self_mountinfo = true;
- r = mount_add_non_exec_dependencies(MOUNT(u));
+ r = mount_add_non_exec_dependencies(mnt);
if (r < 0)
return r;
int r;
assert(u);
+ assert(where);
assert(ret_flags);
if (!m->where) {
m->from_proc_self_mountinfo = true;
- if (IN_SET(u->load_state, UNIT_NOT_FOUND, UNIT_BAD_SETTING, UNIT_ERROR)) {
+ if (UNIT_IS_LOAD_ERROR(u->load_state)) {
/* The unit was previously not found or otherwise not loaded. Now that the unit shows up in
* /proc/self/mountinfo we should reconsider it this, hence set it to UNIT_LOADED. */
u->load_state = UNIT_LOADED;
assert(s);
assert(fd >= 0);
+ assert(sock);
/* This is called by the socket code when instantiating a new service for a stream socket and the socket needs
* to be configured. We take ownership of the passed fd on success. */
return r;
}
- r = unit_add_two_dependencies(UNIT(sock), UNIT_BEFORE, UNIT_TRIGGERS, UNIT(s), false, UNIT_DEPENDENCY_IMPLICIT);
+ r = unit_add_two_dependencies(UNIT(s), UNIT_AFTER, UNIT_TRIGGERED_BY, UNIT(sock), false, UNIT_DEPENDENCY_IMPLICIT);
if (r < 0)
- return r;
+ return log_unit_debug_errno(UNIT(s), r,
+ "Failed to add After=/TriggeredBy= dependencies on socket unit: %m");
s->socket_fd = fd;
- s->socket_peer = socket_peer_ref(peer);
+ s->socket_peer = peer;
s->socket_fd_selinux_context_net = selinux_context_net;
unit_ref_set(&s->accept_socket, UNIT(s), UNIT(sock));
}
int socket_load_service_unit(Socket *s, int cfd, Unit **ret) {
+ int r;
+
/* Figure out what the unit that will be used to handle the connections on the socket looks like.
*
* If cfd < 0, then we don't have a connection yet. In case of Accept=yes sockets, use a fake
/* Build the instance name and load the unit */
_cleanup_free_ char *prefix = NULL, *instance = NULL, *name = NULL;
- int r;
r = unit_name_to_prefix(UNIT(s)->id, &prefix);
if (r < 0)
if (!pending) {
if (!UNIT_ISSET(s->service)) {
- r = log_unit_warning_errno(UNIT(s), SYNTHETIC_ERRNO(ENOENT),
- "Service to activate vanished, refusing activation.");
+ log_unit_warning(UNIT(s),
+ "Service to activate vanished, refusing activation.");
goto fail;
}
}
r = socket_load_service_unit(s, cfd, &service);
- if (r < 0) {
- if (ERRNO_IS_DISCONNECT(r))
- return;
-
- log_unit_warning_errno(UNIT(s), r, "Failed to load connection service unit: %m");
+ if (ERRNO_IS_NEG_DISCONNECT(r))
+ return;
+ if (r < 0 || UNIT_IS_LOAD_ERROR(service->load_state)) {
+ log_unit_warning_errno(UNIT(s), r < 0 ? r : service->load_error,
+ "Failed to load connection service unit: %m");
goto fail;
}
-
- r = unit_add_two_dependencies(UNIT(s), UNIT_BEFORE, UNIT_TRIGGERS, service,
- false, UNIT_DEPENDENCY_IMPLICIT);
- if (r < 0) {
- log_unit_warning_errno(UNIT(s), r, "Failed to add Before=/Triggers= dependencies on connection unit: %m");
+ if (service->load_state == UNIT_MASKED) {
+ log_unit_warning(UNIT(s), "Connection service unit is masked, refusing.");
goto fail;
}
goto fail;
}
- TAKE_FD(cfd); /* We passed ownership of the fd to the service now. Forget it here. */
+ /* We passed ownership of the fd and socket peer to the service now. */
+ TAKE_FD(cfd);
+ TAKE_PTR(p);
+
s->n_connections++;
r = manager_add_job(UNIT(s)->manager, JOB_START, service, JOB_REPLACE, NULL, &error, NULL);
return;
queue_error:
- if (ERRNO_IS_RESOURCE(r))
- log_unit_warning(UNIT(s), "Failed to queue service startup job: %s",
- bus_error_message(&error, r));
- else
- log_unit_warning(UNIT(s), "Failed to queue service startup job (Maybe the service file is missing or not a %s unit?): %s",
- cfd >= 0 ? "template" : "non-template",
- bus_error_message(&error, r));
+ log_unit_warning_errno(UNIT(s), r, "Failed to queue service startup job%s: %s",
+ cfd >= 0 && !ERRNO_IS_RESOURCE(r) ? " (Maybe the service is missing or is a template unit?)" : "",
+ bus_error_message(&error, r));
fail:
socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES);
return 0;
}
-int socket_collect_fds(Socket *s, int **fds) {
- size_t k = 0, n = 0;
- int *rfds;
+int socket_collect_fds(Socket *s, int **ret) {
+ size_t n = 0, k = 0;
assert(s);
- assert(fds);
+ assert(ret);
/* Called from the service code for requesting our fds */
n += p->n_auxiliary_fds;
}
- if (n <= 0) {
- *fds = NULL;
+ if (n == 0) {
+ *ret = NULL;
return 0;
}
- rfds = new(int, n);
- if (!rfds)
+ int *fds = new(int, n);
+ if (!fds)
return -ENOMEM;
LIST_FOREACH(port, p, s->ports) {
if (p->fd >= 0)
- rfds[k++] = p->fd;
- for (size_t i = 0; i < p->n_auxiliary_fds; ++i)
- rfds[k++] = p->auxiliary_fds[i];
+ fds[k++] = p->fd;
+ FOREACH_ARRAY(i, p->auxiliary_fds, p->n_auxiliary_fds)
+ fds[k++] = *i;
}
assert(k == n);
- *fds = rfds;
+ *ret = fds;
return (int) n;
}
DEFINE_TRIVIAL_CLEANUP_FUNC(SocketPeer*, socket_peer_unref);
/* Called from the service code when collecting fds */
-int socket_collect_fds(Socket *s, int **fds);
+int socket_collect_fds(Socket *s, int **ret);
/* Called from the service code when a per-connection service ended */
void socket_connection_unref(Socket *s);
int priority,
bool set_flags) {
+ _cleanup_(unit_freep) Unit *new = NULL;
_cleanup_free_ char *e = NULL;
- bool new;
Unit *u;
Swap *s;
- SwapParameters *p;
int r;
assert(m);
if (s->from_proc_swaps &&
!path_equal(s->parameters_proc_swaps.what, what_proc_swaps))
- return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
- "Swap %s appeared twice with different device paths %s and %s, refusing.",
- e, s->parameters_proc_swaps.what, what_proc_swaps);
-
- new = false;
+ return log_unit_error_errno(u, SYNTHETIC_ERRNO(EEXIST),
+ "Swap appeared twice with different device paths %s and %s, refusing.",
+ s->parameters_proc_swaps.what, what_proc_swaps);
} else {
- new = true;
-
- r = unit_new_for_name(m, sizeof(Swap), e, &u);
- if (r < 0) {
- log_unit_warning_errno(u, r, "Failed to load swap unit: %m");
- goto fail;
- }
+ r = unit_new_for_name(m, sizeof(Swap), e, &new);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to load swap unit '%s': %m", e);
+ u = new;
s = ASSERT_PTR(SWAP(u));
s->what = strdup(what);
- if (s->what) {
- r = log_oom();
- goto fail;
- }
+ if (!s->what)
+ return log_oom();
unit_add_to_load_queue(u);
}
- p = &s->parameters_proc_swaps;
+ SwapParameters *p = &s->parameters_proc_swaps;
- if (!s->parameters_proc_swaps.what) {
+ if (!p->what) {
p->what = strdup(what_proc_swaps);
- if (!p->what) {
- r = log_oom();
- goto fail;
- }
+ if (!p->what)
+ return log_oom();
}
- /* The unit is definitely around now, mark it as loaded if it was previously referenced but could not be
- * loaded. After all we can load it now, from the data in /proc/swaps. */
- if (IN_SET(u->load_state, UNIT_NOT_FOUND, UNIT_BAD_SETTING, UNIT_ERROR)) {
+ /* The unit is definitely around now, mark it as loaded if it was previously referenced but
+ * could not be loaded. After all we can load it now, from the data in /proc/swaps. */
+ if (UNIT_IS_LOAD_ERROR(u->load_state)) {
u->load_state = UNIT_LOADED;
u->load_error = 0;
}
if (set_flags) {
s->is_active = true;
- s->just_activated = !SWAP(u)->from_proc_swaps;
+ s->just_activated = !s->from_proc_swaps;
}
s->from_proc_swaps = true;
p->priority_set = true;
unit_add_to_dbus_queue(u);
- return 0;
+ TAKE_PTR(new);
-fail:
- if (!new)
- unit_free(u);
-
- return r;
+ return 0;
}
static void swap_process_new(Manager *m, const char *device, int prio, bool set_flags) {
return t >= 0 && t < _UNIT_LOAD_STATE_MAX && t != UNIT_STUB && t != UNIT_MERGED;
}
+static inline bool UNIT_IS_LOAD_ERROR(UnitLoadState t) {
+ return IN_SET(t, UNIT_NOT_FOUND, UNIT_BAD_SETTING, UNIT_ERROR);
+}
+
/* Stores the 'reason' a dependency was created as a bit mask, i.e. due to which configuration source it came to be. We
* use this so that we can selectively flush out parts of dependencies again. Note that the same dependency might be
* created as a result of multiple "reasons", hence the bitmask. */
#include "time-util.h"
#include "user-record.h"
+/* Flags supported by UpdateEx() */
+#define SD_HOMED_UPDATE_OFFLINE (UINT64_C(1) << 0)
+#define SD_HOMED_UPDATE_FLAGS_ALL (SD_HOMED_UPDATE_OFFLINE)
+
+/* Flags supported by CreateHomeEx() */
+#define SD_HOMED_CREATE_FLAGS_ALL (0)
+
/* Put some limits on disk sizes: not less than 5M, not more than 5T */
#define USER_DISK_SIZE_MIN (UINT64_C(5)*1024*1024)
#define USER_DISK_SIZE_MAX (UINT64_C(5)*1024*1024*1024*1024)
static bool arg_ask_password = true;
static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
static const char *arg_host = NULL;
+static bool arg_offline = false;
static const char *arg_identity = NULL;
static JsonVariant *arg_identity_extra = NULL;
static JsonVariant *arg_identity_extra_privileged = NULL;
_cleanup_free_ char *buffer = NULL;
_cleanup_hashmap_free_ Hashmap *blobs = NULL;
const char *username;
+ uint64_t flags = 0;
int r;
if (argc >= 2)
if (arg_and_resize || arg_and_change_password)
log_info("Updating home directory.");
+ if (arg_offline)
+ flags |= SD_HOMED_UPDATE_OFFLINE;
+
for (;;) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
if (r < 0)
return bus_log_create_error(r);
- r = sd_bus_message_append(m, "t", UINT64_C(0));
+ r = sd_bus_message_append(m, "t", flags);
if (r < 0)
return bus_log_create_error(r);
" --no-pager Do not pipe output into a pager\n"
" --no-legend Do not show the headers and footers\n"
" --no-ask-password Do not ask for system passwords\n"
+ " --offline Don't update record embedded in home directory\n"
" -H --host=[USER@]HOST Operate on remote host\n"
" -M --machine=CONTAINER Operate on local container\n"
" --identity=PATH Read JSON identity from file\n"
ARG_NO_PAGER,
ARG_NO_LEGEND,
ARG_NO_ASK_PASSWORD,
+ ARG_OFFLINE,
ARG_REALM,
ARG_EMAIL_ADDRESS,
ARG_DISK_SIZE,
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "no-legend", no_argument, NULL, ARG_NO_LEGEND },
{ "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
+ { "offline", no_argument, NULL, ARG_OFFLINE },
{ "host", required_argument, NULL, 'H' },
{ "machine", required_argument, NULL, 'M' },
{ "identity", required_argument, NULL, 'I' },
arg_ask_password = false;
break;
+ case ARG_OFFLINE:
+ arg_offline = true;
+ break;
+
case 'H':
arg_transport = BUS_TRANSPORT_REMOTE;
arg_host = optarg;
#include "bus-polkit.h"
#include "fd-util.h"
#include "format-util.h"
+#include "home-util.h"
#include "homed-bus.h"
#include "homed-home-bus.h"
#include "homed-home.h"
if (r < 0)
return r;
- if (flags != 0)
- return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Provided flags are unsupported.");
+ if ((flags & ~SD_HOMED_UPDATE_FLAGS_ALL) != 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags provided.");
r = home_verify_polkit_async(
h,
if (r < 0)
return r;
+ h->current_operation->call_flags = flags;
+
return 1;
}
assert(message);
- r = bus_message_read_home_record(message, USER_RECORD_REQUIRE_REGULAR|USER_RECORD_REQUIRE_SECRET|USER_RECORD_ALLOW_PRIVILEGED|USER_RECORD_ALLOW_PER_MACHINE|USER_RECORD_ALLOW_SIGNATURE|USER_RECORD_PERMISSIVE, &hr, error);
+ r = bus_message_read_home_record(message, USER_RECORD_REQUIRE_REGULAR|USER_RECORD_ALLOW_SECRET|USER_RECORD_ALLOW_PRIVILEGED|USER_RECORD_ALLOW_PER_MACHINE|USER_RECORD_ALLOW_SIGNATURE|USER_RECORD_PERMISSIVE, &hr, error);
if (r < 0)
return r;
if (r == 0)
return 1; /* Will call us back */
- r = home_resize(h, sz, secret, /* automatic= */ false, error);
+ r = home_resize(h, sz, secret, error);
if (r < 0)
return r;
static void home_change_finish(Home *h, int ret, UserRecord *hr) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ uint64_t flags;
int r;
assert(h);
+ flags = h->current_operation ? h->current_operation->call_flags : 0;
+
if (ret < 0) {
(void) home_count_bad_authentication(h, ret, /* save= */ true);
}
if (hr) {
- r = home_set_record(h, hr);
- if (r < 0)
- log_warning_errno(r, "Failed to update home record, ignoring: %m");
- else {
+ if (!FLAGS_SET(flags, SD_HOMED_UPDATE_OFFLINE)) {
r = user_record_good_authentication(h->record);
if (r < 0)
log_warning_errno(r, "Failed to increase good authentication counter, ignoring: %m");
+ }
+ r = home_set_record(h, hr);
+ if (r >= 0)
r = home_save_record(h);
- if (r < 0)
- log_warning_errno(r, "Failed to write home record to disk, ignoring: %m");
+ if (r < 0) {
+ if (FLAGS_SET(flags, SD_HOMED_UPDATE_OFFLINE)) {
+ log_error_errno(r, "Failed to update home record and write it to disk: %m");
+ sd_bus_error_set(&error, SD_BUS_ERROR_FAILED, "Failed to cache changes to home record");
+ goto finish;
+ } else
+ log_warning_errno(r, "Failed to update home record, ignoring: %m");
}
}
_exit(EXIT_FAILURE);
}
+ if (setenv("SYSTEMD_HOMEWORK_UPDATE_OFFLINE", one_zero(FLAGS_SET(flags, SD_HOMED_UPDATE_OFFLINE)), 1) < 0) {
+ log_error_errno(errno, "Failed to set $SYSTEMD_HOMEWORK_UPDATE_OFFLINE: %m");
+ _exit(EXIT_FAILURE);
+ }
+
r = setenv_systemd_exec_pid(true);
if (r < 0)
log_warning_errno(r, "Failed to update $SYSTEMD_EXEC_PID, ignoring: %m");
case HOME_UNFIXATED:
return sd_bus_error_setf(error, BUS_ERROR_HOME_UNFIXATED, "Home %s has not been fixated yet.", h->user_name);
case HOME_ABSENT:
- return sd_bus_error_setf(error, BUS_ERROR_HOME_ABSENT, "Home %s is currently missing or not plugged in.", h->user_name);
+ if (!FLAGS_SET(flags, SD_HOMED_UPDATE_OFFLINE))
+ return sd_bus_error_setf(error, BUS_ERROR_HOME_ABSENT, "Home %s is currently missing or not plugged in.", h->user_name);
+ break; /* offline updates are compatible w/ an absent home area */
case HOME_LOCKED:
return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name);
case HOME_INACTIVE:
int home_resize(Home *h,
uint64_t disk_size,
UserRecord *secret,
- bool automatic,
sd_bus_error *error) {
_cleanup_(user_record_unrefp) UserRecord *c = NULL;
c = TAKE_PTR(signed_c);
}
- r = home_update_internal(h, automatic ? "resize-auto" : "resize", c, secret, NULL, 0, error);
+ r = home_update_internal(h, "resize", c, secret, NULL, 0, error);
if (r < 0)
return r;
(void) sd_event_source_set_description(*ss, "acquire-ref");
- r = sd_event_source_set_priority(*ss, SD_EVENT_PRIORITY_IDLE-1);
+ /* We need to notice dropped refs before we process new bus requests (which
+ * might try to obtain new refs) */
+ r = sd_event_source_set_priority(*ss, SD_EVENT_PRIORITY_NORMAL-10);
if (r < 0)
return r;
int home_create(Home *h, UserRecord *secret, Hashmap *blobs, uint64_t flags, sd_bus_error *error);
int home_remove(Home *h, sd_bus_error *error);
int home_update(Home *h, UserRecord *new_record, Hashmap *blobs, uint64_t flags, sd_bus_error *error);
-int home_resize(Home *h, uint64_t disk_size, UserRecord *secret, bool automatic, sd_bus_error *error);
+int home_resize(Home *h, uint64_t disk_size, UserRecord *secret, sd_bus_error *error);
int home_passwd(Home *h, UserRecord *new_secret, UserRecord *old_secret, sd_bus_error *error);
int home_unregister(Home *h, sd_bus_error *error);
int home_lock(Home *h, sd_bus_error *error);
#include "bus-common-errors.h"
#include "bus-polkit.h"
#include "format-util.h"
+#include "home-util.h"
#include "homed-bus.h"
#include "homed-home-bus.h"
#include "homed-manager-bus.h"
r = sd_bus_message_read(message, "t", &flags);
if (r < 0)
return r;
- if (flags != 0)
- return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Provided flags are unsupported.");
+ if ((flags & ~SD_HOMED_CREATE_FLAGS_ALL) != 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags provided.");
}
r = bus_verify_polkit_async(
if (r < 0)
return r;
+ h->current_operation->call_flags = flags;
+
return 1;
fail:
if (r < 0)
return log_error_errno(r, "Failed to request name: %m");
- r = sd_bus_attach_event(m->bus, m->event, 0);
+ r = sd_bus_attach_event(m->bus, m->event, SD_EVENT_PRIORITY_NORMAL);
if (r < 0)
return log_error_errno(r, "Failed to attach bus to event loop: %m");
h->rebalance_pending = false;
- r = home_resize(h, h->rebalance_goal, /* secret= */ NULL, /* automatic= */ true, &error);
+ r = home_resize(h, h->rebalance_goal, /* secret= */ NULL, &error);
if (r < 0)
log_warning_errno(r, "Failed to resize home '%s' for rebalancing, ignoring: %s",
h->user_name, bus_error_message(&error, r));
sd_bus_message *message;
UserRecord *secret;
+ uint64_t call_flags; /* flags passed into UpdateEx() or CreateHomeEx() */
int send_fd; /* pipe fd for AcquireHome() which is taken already when we start the operation */
int result; /* < 0 if not completed yet, == 0 on failure, > 0 on success */
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(key_serial_t, keyring_unlink, -1);
-static int upload_to_keyring(
- UserRecord *h,
- const char *password,
- key_serial_t *ret_key_serial) {
+static int upload_to_keyring(UserRecord *h, const void *vk, size_t vks, key_serial_t *ret) {
_cleanup_free_ char *name = NULL;
key_serial_t serial;
assert(h);
- assert(password);
-
- /* If auto-shrink-on-logout is turned on, we need to keep the key we used to unlock the LUKS volume
- * around, since we'll need it when automatically resizing (since we can't ask the user there
- * again). We do this by uploading it into the kernel keyring, specifically the "session" one. This
- * is done under the assumption systemd-homed gets its private per-session keyring (i.e. default
- * service behaviour, given that KeyringMode=private is the default). It will survive between our
- * systemd-homework invocations that way.
- *
- * If auto-shrink-on-logout is disabled we'll skip this step, to be frugal with sensitive data. */
-
- if (user_record_auto_resize_mode(h) != AUTO_RESIZE_SHRINK_AND_GROW) { /* Won't need it */
- if (ret_key_serial)
- *ret_key_serial = -1;
- return 0;
- }
+ assert(vk);
+ assert(vks > 0);
+
+ /* We upload the LUKS volume key into the kernel session keyring, under the assumption that
+ * systemd-homed gets its own private session keyring (i.e. the default service behavior, given
+ * that KeyringMode=private is the default). That way, the key will survive between invocations
+ * of systemd-homework. */
name = strjoin("homework-user-", h->user_name);
if (!name)
return -ENOMEM;
- serial = add_key("user", name, password, strlen(password), KEY_SPEC_SESSION_KEYRING);
+ serial = add_key("user", name, vk, vks, KEY_SPEC_SESSION_KEYRING);
if (serial == -1)
return -errno;
- if (ret_key_serial)
- *ret_key_serial = serial;
-
+ if (ret)
+ *ret = serial;
return 1;
}
struct crypt_device *cd,
char **passwords,
void *volume_key,
- size_t *volume_key_size,
- key_serial_t *ret_key_serial) {
+ size_t *volume_key_size) {
int r;
assert(h);
assert(cd);
+ assert(volume_key);
+ assert(volume_key_size);
STRV_FOREACH(pp, passwords) {
size_t vks = *volume_key_size;
*pp,
strlen(*pp));
if (r >= 0) {
- if (ret_key_serial) {
- /* If ret_key_serial is non-NULL, let's try to upload the password that
- * worked, and return its serial. */
- r = upload_to_keyring(h, *pp, ret_key_serial);
- if (r < 0) {
- log_debug_errno(r, "Failed to upload LUKS password to kernel keyring, ignoring: %m");
- *ret_key_serial = -1;
- }
- }
-
*volume_key_size = vks;
return 0;
}
return -ENOKEY;
}
+static int luks_get_volume_key(
+ UserRecord *h,
+ struct crypt_device *cd,
+ const PasswordCache *cache,
+ void *volume_key,
+ size_t *volume_key_size,
+ key_serial_t *ret_key_serial) {
+
+ char **list;
+ size_t vks;
+ int r;
+
+ assert(h);
+ assert(cd);
+ assert(volume_key);
+ assert(volume_key_size);
+
+ if (cache && cache->volume_key) {
+ /* Shortcut: If volume key was loaded from the keyring then just use it */
+ if (cache->volume_key_size > *volume_key_size)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOBUFS),
+ "LUKS volume key from kernel keyring too big for buffer (need %zu bytes, have %zu)",
+ cache->volume_key_size, *volume_key_size);
+ memcpy(volume_key, cache->volume_key, cache->volume_key_size);
+ *volume_key_size = cache->volume_key_size;
+ if (ret_key_serial)
+ *ret_key_serial = -1; /* Key came from keyring. No need to re-upload it */
+ return 0;
+ }
+
+ vks = *volume_key_size;
+
+ FOREACH_ARGUMENT(list,
+ cache ? cache->pkcs11_passwords : NULL,
+ cache ? cache->fido2_passwords : NULL,
+ h->password) {
+
+ r = luks_try_passwords(h, cd, list, volume_key, &vks);
+ if (r == -ENOKEY)
+ continue;
+ if (r < 0)
+ return r;
+
+ /* We got a volume key! */
+
+ if (ret_key_serial) {
+ r = upload_to_keyring(h, volume_key, vks, ret_key_serial);
+ if (r < 0) {
+ log_warning_errno(r, "Failed to upload LUKS volume key to kernel keyring, ignoring: %m");
+ *ret_key_serial = -1;
+ }
+ }
+
+ *volume_key_size = vks;
+ return 0;
+ }
+
+ return -ENOKEY;
+}
+
static int luks_setup(
UserRecord *h,
const char *node,
const char *cipher,
const char *cipher_mode,
uint64_t volume_key_size,
- char **passwords,
const PasswordCache *cache,
bool discard,
struct crypt_device **ret,
if (!vk)
return log_oom();
- r = -ENOKEY;
- char **list;
- FOREACH_ARGUMENT(list,
- cache ? cache->keyring_passswords : NULL,
- cache ? cache->pkcs11_passwords : NULL,
- cache ? cache->fido2_passwords : NULL,
- passwords) {
-
- r = luks_try_passwords(h, cd, list, vk, &vks, ret_key_serial ? &key_serial : NULL);
- if (r != -ENOKEY)
- break;
- }
+ r = luks_get_volume_key(h, cd, cache, vk, &vks, ret_key_serial ? &key_serial : NULL);
if (r == -ENOKEY)
return log_error_errno(r, "No valid password for LUKS superblock.");
if (r < 0)
if (!vk)
return log_oom();
- r = -ENOKEY;
- char **list;
- FOREACH_ARGUMENT(list,
- cache ? cache->keyring_passswords : NULL,
- cache ? cache->pkcs11_passwords : NULL,
- cache ? cache->fido2_passwords : NULL,
- h->password) {
-
- r = luks_try_passwords(h, setup->crypt_device, list, vk, &vks, NULL);
- if (r != -ENOKEY)
- break;
- }
+ r = luks_get_volume_key(h, setup->crypt_device, cache, vk, &vks, NULL);
if (r == -ENOKEY)
return log_error_errno(r, "No valid password for LUKS superblock.");
if (r < 0)
h->luks_cipher,
h->luks_cipher_mode,
h->luks_volume_key_size,
- h->password,
cache,
user_record_luks_discard(h) || user_record_luks_offline_discard(h),
&setup->crypt_device,
if (!volume_key)
return log_oom();
- r = -ENOKEY;
- char **list;
- FOREACH_ARGUMENT(list,
- cache ? cache->keyring_passswords : NULL,
- cache ? cache->pkcs11_passwords : NULL,
- cache ? cache->fido2_passwords : NULL,
- h->password) {
-
- r = luks_try_passwords(h, setup->crypt_device, list, volume_key, &volume_key_size, NULL);
- if (r != -ENOKEY)
- break;
- }
+ r = luks_get_volume_key(h, setup->crypt_device, cache, volume_key, &volume_key_size, NULL);
if (r == -ENOKEY)
return log_error_errno(SYNTHETIC_ERRNO(ENOKEY), "Failed to unlock LUKS superblock with supplied passwords.");
if (r < 0)
return log_error_errno(r, "Failed to set up LUKS password: %m");
log_info("Updated LUKS key slot %zu.", i);
-
- /* If we changed the password, then make sure to update the copy in the keyring, so that
- * auto-rebalance continues to work. We only do this if we operate on an active home dir. */
- if (i == 0 && FLAGS_SET(flags, HOME_SETUP_ALREADY_ACTIVATED))
- upload_to_keyring(h, effective_passwords[i], NULL);
}
return 1;
return 0;
}
-static int luks_try_resume(
- struct crypt_device *cd,
- const char *dm_name,
- char **password) {
-
- int r;
-
- assert(cd);
- assert(dm_name);
-
- STRV_FOREACH(pp, password) {
- r = sym_crypt_resume_by_passphrase(
- cd,
- dm_name,
- CRYPT_ANY_SLOT,
- *pp,
- strlen(*pp));
- if (r >= 0) {
- log_info("Resumed LUKS device %s.", dm_name);
- return 0;
- }
-
- log_debug_errno(r, "Password %zu didn't work for resuming device: %m", (size_t) (pp - password));
- }
-
- return -ENOKEY;
-}
-
int home_unlock_luks(UserRecord *h, HomeSetup *setup, const PasswordCache *cache) {
+ _cleanup_(keyring_unlinkp) key_serial_t key_serial = -1;
+ _cleanup_(erase_and_freep) void *vk = NULL;
+ size_t vks;
int r;
assert(h);
log_info("Discovered used LUKS device %s.", setup->dm_node);
- r = -ENOKEY;
- char **list;
- FOREACH_ARGUMENT(list,
- cache ? cache->pkcs11_passwords : NULL,
- cache ? cache->fido2_passwords : NULL,
- h->password) {
+ r = sym_crypt_get_volume_key_size(setup->crypt_device);
+ if (r <= 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to determine LUKS volume key size");
+ vks = (size_t) r;
- r = luks_try_resume(setup->crypt_device, setup->dm_name, list);
- if (r != -ENOKEY)
- break;
- }
+ vk = malloc(vks);
+ if (!vk)
+ return log_oom();
+
+ r = luks_get_volume_key(h, setup->crypt_device, cache, vk, &vks, &key_serial);
if (r == -ENOKEY)
return log_error_errno(r, "No valid password for LUKS superblock.");
+ if (r < 0)
+ return log_error_errno(r, "Failed to unlock LUKS superblock: %m");
+
+ r = sym_crypt_resume_by_volume_key(setup->crypt_device, setup->dm_name, vk, vks);
if (r < 0)
return log_error_errno(r, "Failed to resume LUKS superblock: %m");
+ TAKE_KEY_SERIAL(key_serial); /* Leave key in kernel keyring */
+
log_info("LUKS device resumed.");
return 0;
}
if (!cache)
return;
+ cache->volume_key = erase_and_free(cache->volume_key);
cache->pkcs11_passwords = strv_free_erase(cache->pkcs11_passwords);
cache->fido2_passwords = strv_free_erase(cache->fido2_passwords);
- cache->keyring_passswords = strv_free_erase(cache->keyring_passswords);
}
void password_cache_load_keyring(UserRecord *h, PasswordCache *cache) {
- _cleanup_(erase_and_freep) void *p = NULL;
_cleanup_free_ char *name = NULL;
- char **strv;
+ _cleanup_(erase_and_freep) void *vk = NULL;
+ size_t vks;
key_serial_t serial;
- size_t sz;
int r;
assert(h);
assert(cache);
- /* Loads the password we need to for automatic resizing from the kernel keyring */
-
name = strjoin("homework-user-", h->user_name);
if (!name)
return (void) log_oom();
serial = request_key("user", name, NULL, 0);
- if (serial == -1)
- return (void) log_debug_errno(errno, "Failed to request key '%s', ignoring: %m", name);
-
- r = keyring_read(serial, &p, &sz);
+ if (serial == -1) {
+ if (errno == ENOKEY) {
+ log_info("Home volume key is not available in kernel keyring.");
+ return;
+ }
+ return (void) log_warning_errno(errno, "Failed to request key '%s', ignoring: %m", name);
+ }
+
+ r = keyring_read(serial, &vk, &vks);
if (r < 0)
- return (void) log_debug_errno(r, "Failed to read keyring key '%s', ignoring: %m", name);
-
- if (memchr(p, 0, sz))
- return (void) log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Cached password contains embedded NUL byte, ignoring.");
-
- strv = new(char*, 2);
- if (!strv)
- return (void) log_oom();
-
- strv[0] = TAKE_PTR(p); /* Note that keyring_read() will NUL terminate implicitly, hence we don't have
- * to NUL terminate manually here: it's a valid string. */
- strv[1] = NULL;
+ return (void) log_warning_errno(r, "Failed to read keyring key '%s', ignoring: %m", name);
- strv_free_erase(cache->keyring_passswords);
- cache->keyring_passswords = strv;
+ log_info("Successfully acquired home volume key from kernel keyring.");
- log_debug("Successfully acquired home key from kernel keyring.");
+ erase_and_free(cache->volume_key);
+ cache->volume_key = TAKE_PTR(vk);
+ cache->volume_key_size = vks;
}
#include "user-record.h"
typedef struct PasswordCache {
- /* Passwords acquired from the kernel keyring */
- char **keyring_passswords;
+ /* The volume key from the kernel keyring */
+ void *volume_key;
+ size_t volume_key_size;
/* Decoding passwords from security tokens is expensive and typically requires user interaction,
* hence cache any we already figured out. */
if (!cache)
return false;
+ /* Used to decide whether or not to set a minimal PBKDF, under the assumption that if
+ * the cache contains a password then the password came from a hardware token of some kind
+ * and is thus naturally high-entropy. */
+
return strv_contains(cache->pkcs11_passwords, p) ||
- strv_contains(cache->fido2_passwords, p) ||
- strv_contains(cache->keyring_passswords, p);
+ strv_contains(cache->fido2_passwords, p);
}
void password_cache_load_keyring(UserRecord *h, PasswordCache *cache);
* times over the course of an operation (think: on login we authenticate the host user record, the
* record embedded in the LUKS record and the one embedded in $HOME). Hence we keep a list of
* passwords we already decrypted, so that we don't have to do the (slow and potentially interactive)
- * PKCS#11/FIDO2 dance for the relevant token again and again. */
+ * PKCS#11/FIDO2 dance for the relevant token again and again.
+ *
+ * The 'cache' parameter might also contain the LUKS volume key, loaded from the kernel keyring.
+ * In this case, authentication becomes optional - if a secret section is provided it will be
+ * verified, but if missing then authentication is skipped entirely. Thus, callers should
+ * consider carefuly whether it is safe to load the volume key into 'cache' before doing so.
+ * Note that most of the time this is safe, because the home area must be active for the key
+ * to exist in the keyring, and the user would have had to authenticate when activating their
+ * home area; however, for some methods (i.e. ChangePassword, Authenticate) it makes more sense
+ * to force re-authentication. */
+
+ /* First, let's see if we already have a volume key from the keyring */
+ if (cache && cache->volume_key &&
+ json_variant_is_blank_object(json_variant_by_key(secret->json, "secret"))) {
+ log_info("LUKS volume key from keyring unlocks user record.");
+ return 1;
+ }
- /* First, let's see if the supplied plain-text passwords work? */
+ /* Next, let's see if the supplied plain-text passwords work? */
r = user_record_test_password(h, secret);
if (r == -ENOKEY)
need_password = true;
else
log_info("None of the supplied plaintext passwords unlock the user record's hashed recovery keys.");
- /* Second, test cached PKCS#11 passwords */
+ /* Next, test cached PKCS#11 passwords */
for (size_t n = 0; n < h->n_pkcs11_encrypted_key; n++)
STRV_FOREACH(pp, cache->pkcs11_passwords) {
r = test_password_one(h->pkcs11_encrypted_key[n].hashed_password, *pp);
}
}
- /* Third, test cached FIDO2 passwords */
+ /* Next, test cached FIDO2 passwords */
for (size_t n = 0; n < h->n_fido2_hmac_salt; n++)
/* See if any of the previously calculated passwords work */
STRV_FOREACH(pp, cache->fido2_passwords) {
}
}
- /* Fourth, let's see if any of the PKCS#11 security tokens are plugged in and help us */
+ /* Next, let's see if any of the PKCS#11 security tokens are plugged in and help us */
for (size_t n = 0; n < h->n_pkcs11_encrypted_key; n++) {
#if HAVE_P11KIT
_cleanup_(pkcs11_callback_data_release) struct pkcs11_callback_data data = {
#endif
}
- /* Fifth, let's see if any of the FIDO2 security tokens are plugged in and help us */
+ /* Next, let's see if any of the FIDO2 security tokens are plugged in and help us */
for (size_t n = 0; n < h->n_fido2_hmac_salt; n++) {
#if HAVE_LIBFIDO2
_cleanup_(erase_and_freep) char *decrypted_password = NULL;
return 0;
}
-static int home_validate_update(UserRecord *h, HomeSetup *setup, HomeSetupFlags *flags) {
- bool has_mount = false;
- int r;
-
+static int home_basic_validate_update(UserRecord *h) {
assert(h);
- assert(setup);
if (!h->user_name)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record lacks user name, refusing.");
+
if (!uid_is_valid(h->uid))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record lacks UID, refusing.");
+
if (!IN_SET(user_record_storage(h), USER_LUKS, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT, USER_CIFS))
return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Processing home directories of type '%s' currently not supported.", user_storage_to_string(user_record_storage(h)));
+ return 0;
+}
+
+static int home_validate_update(UserRecord *h, HomeSetup *setup, HomeSetupFlags *flags) {
+ bool has_mount = false;
+ int r;
+
+ assert(h);
+ assert(setup);
+
+ r = home_basic_validate_update(h);
+ if (r < 0)
+ return r;
+
r = user_record_test_home_directory_and_warn(h);
if (r < 0)
return r;
_cleanup_(home_setup_done) HomeSetup setup = HOME_SETUP_INIT;
_cleanup_(password_cache_free) PasswordCache cache = {};
HomeSetupFlags flags = 0;
+ bool offline;
int r;
assert(h);
assert(ret);
- r = user_record_authenticate(h, h, &cache, /* strict_verify= */ true);
- if (r < 0)
- return r;
- assert(r > 0); /* Insist that a password was verified */
+ offline = getenv_bool("SYSTEMD_HOMEWORK_UPDATE_OFFLINE") > 0;
- r = home_validate_update(h, &setup, &flags);
+ if (!offline) {
+ password_cache_load_keyring(h, &cache);
+
+ r = user_record_authenticate(h, h, &cache, /* strict_verify= */ true);
+ if (r < 0)
+ return r;
+ assert(r > 0); /* Insist that a password was verified */
+
+ r = home_validate_update(h, &setup, &flags);
+ } else {
+ /* In offline mode we skip all authentication, since we're
+ * not propagating anything into the home area. The new home
+ * records's authentication will still be checked when the user
+ * next logs in, so this is fine */
+
+ r = home_basic_validate_update(h);
+ }
if (r < 0)
return r;
if (r < 0)
return r;
+ if (offline) {
+ log_info("Offline update requested. Not touching embedded records.");
+ return user_record_clone(h, USER_RECORD_LOAD_MASK_SECRET|USER_RECORD_PERMISSIVE, ret);
+ }
+
r = home_setup(h, flags, &setup, &cache, &header_home);
if (r < 0)
return r;
return 0;
}
-static int home_resize(UserRecord *h, bool automatic, UserRecord **ret) {
+static int home_resize(UserRecord *h, UserRecord **ret) {
_cleanup_(home_setup_done) HomeSetup setup = HOME_SETUP_INIT;
_cleanup_(password_cache_free) PasswordCache cache = {};
HomeSetupFlags flags = 0;
if (h->disk_size == UINT64_MAX)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No target size specified, refusing.");
- if (automatic)
- /* In automatic mode don't want to ask the user for the password, hence load it from the kernel keyring */
- password_cache_load_keyring(h, &cache);
- else {
- /* In manual mode let's ensure the user is fully authenticated */
- r = user_record_authenticate(h, h, &cache, /* strict_verify= */ true);
- if (r < 0)
- return r;
- assert(r > 0); /* Insist that a password was verified */
- }
+ password_cache_load_keyring(h, &cache);
- r = home_validate_update(h, &setup, &flags);
+ r = user_record_authenticate(h, h, &cache, /* strict_verify= */ true);
if (r < 0)
return r;
+ assert(r > 0); /* Insist that a password was verified */
- /* In automatic mode let's skip syncing identities, because we can't validate them, since we can't
- * ask the user for reauthentication */
- if (automatic)
- flags |= HOME_SETUP_RESIZE_DONT_SYNC_IDENTITIES;
+ r = home_validate_update(h, &setup, &flags);
+ if (r < 0)
+ return r;
switch (user_record_storage(h)) {
unit_freezer_done(&freezer); /* Don't thaw the user session. */
+ /* Explicitly flush any per-user key from the keyring */
+ (void) keyring_flush(h);
+
log_info("Everything completed.");
return 1;
}
r = home_remove(home);
else if (streq(argv[1], "update"))
r = home_update(home, blobs, &new_home);
- else if (streq(argv[1], "resize")) /* Resize on user request */
- r = home_resize(home, false, &new_home);
- else if (streq(argv[1], "resize-auto")) /* Automatic resize */
- r = home_resize(home, true, &new_home);
+ else if (streq(argv[1], "resize"))
+ r = home_resize(home, &new_home);
else if (streq(argv[1], "passwd"))
r = home_passwd(home, &new_home);
else if (streq(argv[1], "inspect"))
for (int i = 0; i < LOG_NFACILITIES; i++) {
_cleanup_free_ char *t = NULL;
- if (log_facility_unshifted_to_string_alloc(i, &t))
+ if (log_facility_unshifted_to_string_alloc(i, &t) < 0)
return log_oom();
puts(t);
}
#include "alloc-util.h"
#include "fd-util.h"
#include "fuzz.h"
+#include "icmp6-packet.h"
#include "icmp6-util-unix.h"
#include "ndisc-internal.h"
+#include "ndisc-option.h"
#include "socket-util.h"
-int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+static void test_with_sd_ndisc(const uint8_t *data, size_t size) {
struct ether_addr mac_addr = {
.ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'}
};
_cleanup_(sd_event_unrefp) sd_event *e = NULL;
_cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL;
- if (outside_size_range(size, 0, 2048))
- return 0;
-
- fuzz_setup_logging();
-
assert_se(sd_event_new(&e) >= 0);
assert_se(sd_ndisc_new(&nd) >= 0);
assert_se(sd_ndisc_attach_event(nd, e, 0) >= 0);
(void) sd_event_run(e, UINT64_MAX);
assert_se(sd_ndisc_stop(nd) >= 0);
close(test_fd[1]);
+}
+
+static void test_with_icmp6_packet(const uint8_t *data, size_t size) {
+ static const struct sockaddr_in6 dst = {
+ .sin6_family = AF_INET6,
+ .sin6_addr = IN6ADDR_ALL_ROUTERS_MULTICAST_INIT,
+ };
+
+ _cleanup_close_pair_ int fd_pair[2] = EBADF_PAIR;
+ _cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = NULL;
+ _cleanup_set_free_ Set *options = NULL;
+
+ assert_se(socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, fd_pair) >= 0);
+ assert_se(write(fd_pair[1], data, size) == (ssize_t) size);
+
+ if (icmp6_packet_receive(fd_pair[0], &packet) < 0)
+ return;
+
+ if (ndisc_parse_options(packet, &options) < 0)
+ return;
+
+ if (ndisc_send(fd_pair[1], &dst, icmp6_packet_get_header(packet), options) < 0)
+ return;
+
+ packet = icmp6_packet_unref(packet);
+ options = set_free(options);
+
+ if (icmp6_packet_receive(fd_pair[0], &packet) < 0)
+ return;
+
+ (void) ndisc_parse_options(packet, &options);
+}
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ if (outside_size_range(size, 0, 2048))
+ return 0;
+
+ fuzz_setup_logging();
+ test_with_sd_ndisc(data, size);
+ test_with_icmp6_packet(data, size);
return 0;
}
return 0;
}
-static const struct icmp6_hdr* icmp6_packet_get_header(ICMP6Packet *p) {
+const struct icmp6_hdr* icmp6_packet_get_header(ICMP6Packet *p) {
assert(p);
if (p->raw_size < sizeof(struct icmp6_hdr))
int icmp6_packet_get_sender_address(ICMP6Packet *p, struct in6_addr *ret);
int icmp6_packet_get_timestamp(ICMP6Packet *p, clockid_t clock, usec_t *ret);
+const struct icmp6_hdr* icmp6_packet_get_header(ICMP6Packet *p);
int icmp6_packet_get_type(ICMP6Packet *p);
int icmp6_packet_receive(int fd, ICMP6Packet **ret);
/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#include <netinet/icmp6.h>
#include <netinet/ip6.h>
#include <unistd.h>
#include "fd-util.h"
#include "icmp6-util-unix.h"
-send_ra_t send_ra_function = NULL;
int test_fd[2] = EBADF_PAIR;
static struct in6_addr dummy_link_local = {
return test_fd[is_router];
}
-int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) {
- if (!send_ra_function)
- return 0;
-
- return send_ra_function(0);
+int icmp6_send(int fd, const struct sockaddr_in6 *dst, const struct iovec *iov, size_t n_iov) {
+ return writev(fd, iov, n_iov);
}
int icmp6_receive(
#include "icmp6-util.h"
-typedef int (*send_ra_t)(uint8_t flags);
-
-extern send_ra_t send_ra_function;
extern int test_fd[2];
return TAKE_FD(s);
}
-int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) {
- struct sockaddr_in6 dst = {
- .sin6_family = AF_INET6,
- .sin6_addr = IN6ADDR_ALL_ROUTERS_MULTICAST_INIT,
- };
- struct {
- struct nd_router_solicit rs;
- struct nd_opt_hdr rs_opt;
- struct ether_addr rs_opt_mac;
- } _packed_ rs = {
- .rs.nd_rs_type = ND_ROUTER_SOLICIT,
- .rs_opt.nd_opt_type = ND_OPT_SOURCE_LINKADDR,
- .rs_opt.nd_opt_len = 1,
- };
- struct iovec iov = {
- .iov_base = &rs,
- .iov_len = sizeof(rs),
- };
+int icmp6_send(int fd, const struct sockaddr_in6 *dst, const struct iovec *iov, size_t n_iov) {
struct msghdr msg = {
- .msg_name = &dst,
- .msg_namelen = sizeof(dst),
- .msg_iov = &iov,
- .msg_iovlen = 1,
+ .msg_name = (struct sockaddr_in6*) dst,
+ .msg_namelen = sizeof(struct sockaddr_in6),
+ .msg_iov = (struct iovec*) iov,
+ .msg_iovlen = n_iov,
};
- assert(s >= 0);
- assert(ether_addr);
-
- rs.rs_opt_mac = *ether_addr;
-
- if (sendmsg(s, &msg, 0) < 0)
+ if (sendmsg(fd, &msg, 0) < 0)
return -errno;
return 0;
***/
#include <net/ethernet.h>
+#include <netinet/in.h>
#include <stdbool.h>
+#include <sys/uio.h>
#include "time-util.h"
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } } }
int icmp6_bind(int ifindex, bool is_router);
-int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr);
+int icmp6_send(int fd, const struct sockaddr_in6 *dst, const struct iovec *iov, size_t n_iov);
int icmp6_receive(
int fd,
void *buffer,
#include "dns-domain.h"
#include "ether-addr-util.h"
#include "hostname-util.h"
+#include "icmp6-util.h"
#include "in-addr-util.h"
+#include "iovec-util.h"
#include "missing_network.h"
#include "ndisc-option.h"
#include "network-common.h"
return p;
}
+static void ndisc_raw_done(sd_ndisc_raw *raw) {
+ if (!raw)
+ return;
+
+ free(raw->bytes);
+}
+
static void ndisc_rdnss_done(sd_ndisc_rdnss *rdnss) {
if (!rdnss)
return;
return NULL;
switch (option->type) {
+ case 0:
+ ndisc_raw_done(&option->raw);
+ break;
+
case SD_NDISC_OPTION_RDNSS:
ndisc_rdnss_done(&option->rdnss);
break;
return r;
switch (x->type) {
+ case 0:
+ return memcmp_nn(x->raw.bytes, x->raw.length, y->raw.bytes, y->raw.length);
+
case SD_NDISC_OPTION_SOURCE_LL_ADDRESS:
case SD_NDISC_OPTION_TARGET_LL_ADDRESS:
case SD_NDISC_OPTION_REDIRECTED_HEADER:
siphash24_compress_typesafe(option->type, state);
switch (option->type) {
+ case 0:
+ siphash24_compress(option->raw.bytes, option->raw.length, state);
+ break;
+
case SD_NDISC_OPTION_SOURCE_LL_ADDRESS:
case SD_NDISC_OPTION_TARGET_LL_ADDRESS:
case SD_NDISC_OPTION_REDIRECTED_HEADER:
return set_ensure_consume(options, &ndisc_option_hash_ops, p);
}
+int ndisc_option_add_raw(Set **options, size_t offset, size_t length, const uint8_t *bytes) {
+ _cleanup_free_ uint8_t *copy = NULL;
+
+ assert(options);
+ assert(bytes);
+
+ if (length == 0)
+ return -EINVAL;
+
+ copy = newdup(uint8_t, bytes, length);
+ if (!copy)
+ return -ENOMEM;
+
+ sd_ndisc_option *p = ndisc_option_new(/* type = */ 0, offset);
+ if (!p)
+ return -ENOMEM;
+
+ p->raw = (sd_ndisc_raw) {
+ .bytes = TAKE_PTR(copy),
+ .length = length,
+ };
+
+ return ndisc_option_consume(options, p);
+}
+
+static int ndisc_option_build_raw(const sd_ndisc_option *option, uint8_t **ret) {
+ assert(option);
+ assert(option->type == 0);
+ assert(ret);
+
+ _cleanup_free_ uint8_t *buf = newdup(uint8_t, option->raw.bytes, option->raw.length);
+ if (!buf)
+ return -ENOMEM;
+
+ *ret = TAKE_PTR(buf);
+ return 0;
+}
+
int ndisc_option_add_link_layer_address(Set **options, uint8_t opt, size_t offset, const struct ether_addr *mac) {
assert(options);
assert(IN_SET(opt, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, SD_NDISC_OPTION_TARGET_LL_ADDRESS));
return ndisc_option_add_link_layer_address(options, opt[0], offset, &mac);
}
+static int ndisc_option_build_link_layer_address(const sd_ndisc_option *option, uint8_t **ret) {
+ assert(option);
+ assert(IN_SET(option->type, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, SD_NDISC_OPTION_TARGET_LL_ADDRESS));
+ assert(ret);
+
+ assert_cc(2 + sizeof(struct ether_addr) == 8);
+
+ _cleanup_free_ uint8_t *buf = new(uint8_t, 2 + sizeof(struct ether_addr));
+ if (!buf)
+ return -ENOMEM;
+
+ buf[0] = option->type;
+ buf[1] = 1;
+ memcpy(buf + 2, &option->mac, sizeof(struct ether_addr));
+
+ *ret = TAKE_PTR(buf);
+ return 0;
+}
+
int ndisc_option_add_prefix(
Set **options,
size_t offset,
return ndisc_option_add_prefix(options, offset, flags, pi->nd_opt_pi_prefix_len, &pi->nd_opt_pi_prefix, valid, pref);
}
+static int ndisc_option_build_prefix(const sd_ndisc_option *option, uint8_t **ret) {
+ assert(option);
+ assert(option->type == SD_NDISC_OPTION_PREFIX_INFORMATION);
+ assert(ret);
+
+ assert_cc(sizeof(struct nd_opt_prefix_info) % 8 == 0);
+
+ _cleanup_free_ struct nd_opt_prefix_info *buf = new(struct nd_opt_prefix_info, 1);
+ if (!buf)
+ return -ENOMEM;
+
+ *buf = (struct nd_opt_prefix_info) {
+ .nd_opt_pi_type = SD_NDISC_OPTION_PREFIX_INFORMATION,
+ .nd_opt_pi_len = sizeof(struct nd_opt_prefix_info) / 8,
+ .nd_opt_pi_prefix_len = option->prefix.prefixlen,
+ .nd_opt_pi_flags_reserved = option->prefix.flags,
+ .nd_opt_pi_valid_time = usec_to_be32_sec(option->prefix.valid_lifetime),
+ .nd_opt_pi_preferred_time = usec_to_be32_sec(option->prefix.preferred_lifetime),
+ .nd_opt_pi_prefix = option->prefix.address,
+ };
+
+ *ret = (uint8_t*) TAKE_PTR(buf);
+ return 0;
+}
+
int ndisc_option_add_redirected_header(Set **options, size_t offset, const struct ip6_hdr *hdr) {
assert(options);
assert(hdr);
return ndisc_option_add_redirected_header(options, offset, (const struct ip6_hdr*) (opt + sizeof(struct nd_opt_rd_hdr)));
}
+static int ndisc_option_build_redirected_header(const sd_ndisc_option *option, uint8_t **ret) {
+ assert(option);
+ assert(option->type == SD_NDISC_OPTION_REDIRECTED_HEADER);
+ assert(ret);
+
+ assert_cc((sizeof(struct nd_opt_rd_hdr) + sizeof(struct ip6_hdr)) % 8 == 0);
+
+ size_t len = DIV_ROUND_UP(sizeof(struct nd_opt_rd_hdr) + sizeof(struct ip6_hdr), 8);
+
+ _cleanup_free_ uint8_t *buf = new(uint8_t, len * 8);
+ if (!buf)
+ return -ENOMEM;
+
+ uint8_t *p;
+ p = mempcpy(buf,
+ &(const struct nd_opt_rd_hdr) {
+ .nd_opt_rh_type = SD_NDISC_OPTION_REDIRECTED_HEADER,
+ .nd_opt_rh_len = len,
+ },
+ sizeof(struct nd_opt_rd_hdr));
+ memcpy(p, &option->hdr, sizeof(struct ip6_hdr));
+
+ *ret = TAKE_PTR(buf);
+ return 0;
+}
+
int ndisc_option_add_mtu(Set **options, size_t offset, uint32_t mtu) {
assert(options);
return ndisc_option_add_mtu(options, offset, be32toh(pm->nd_opt_mtu_mtu));
}
+static int ndisc_option_build_mtu(const sd_ndisc_option *option, uint8_t **ret) {
+ assert(option);
+ assert(option->type == SD_NDISC_OPTION_MTU);
+ assert(ret);
+
+ assert_cc(sizeof(struct nd_opt_mtu) % 8 == 0);
+
+ _cleanup_free_ struct nd_opt_mtu *buf = new(struct nd_opt_mtu, 1);
+ if (!buf)
+ return -ENOMEM;
+
+ *buf = (struct nd_opt_mtu) {
+ .nd_opt_mtu_type = SD_NDISC_OPTION_MTU,
+ .nd_opt_mtu_len = sizeof(struct nd_opt_mtu) / 8,
+ .nd_opt_mtu_mtu = htobe32(option->mtu),
+ };
+
+ *ret = (uint8_t*) TAKE_PTR(buf);
+ return 0;
+}
+
int ndisc_option_add_route(
Set **options,
size_t offset,
return ndisc_option_add_route(options, offset, preference, prefixlen, &prefix, lifetime);
}
+static int ndisc_option_build_route(const sd_ndisc_option *option, uint8_t **ret) {
+ assert(option);
+ assert(option->type == SD_NDISC_OPTION_ROUTE_INFORMATION);
+ assert(option->route.prefixlen <= 128);
+ assert(ret);
+
+ size_t len = 1 + DIV_ROUND_UP(option->route.prefixlen, 64);
+ be32_t lifetime = usec_to_be32_sec(option->route.lifetime);
+
+ _cleanup_free_ uint8_t *buf = new(uint8_t, len * 8);
+ if (!buf)
+ return -ENOMEM;
+
+ buf[0] = SD_NDISC_OPTION_ROUTE_INFORMATION;
+ buf[1] = len;
+ buf[2] = option->route.prefixlen;
+ buf[3] = option->route.preference << 3;
+ memcpy(buf + 4, &lifetime, sizeof(be32_t));
+ memcpy_safe(buf + 8, &option->route.address, (len - 1) * 8);
+
+ *ret = TAKE_PTR(buf);
+ return 0;
+}
+
int ndisc_option_add_rdnss(
Set **options,
size_t offset,
return ndisc_option_add_rdnss(options, offset, n_addrs, (const struct in6_addr*) (opt + 8), lifetime);
}
+static int ndisc_option_build_rdnss(const sd_ndisc_option *option, uint8_t **ret) {
+ assert(option);
+ assert(option->type == SD_NDISC_OPTION_RDNSS);
+ assert(ret);
+
+ size_t len = option->rdnss.n_addresses * 2 + 1;
+ be32_t lifetime = usec_to_be32_sec(option->rdnss.lifetime);
+
+ _cleanup_free_ uint8_t *buf = new(uint8_t, len * 8);
+ if (!buf)
+ return -ENOMEM;
+
+ buf[0] = SD_NDISC_OPTION_RDNSS;
+ buf[1] = len;
+ buf[2] = 0;
+ buf[3] = 0;
+ memcpy(buf + 4, &lifetime, sizeof(be32_t));
+ memcpy(buf + 8, option->rdnss.addresses, sizeof(struct in6_addr) * option->rdnss.n_addresses);
+
+ *ret = TAKE_PTR(buf);
+ return 0;
+}
+
int ndisc_option_add_flags_extension(Set **options, size_t offset, uint64_t flags) {
assert(options);
return ndisc_option_add_flags_extension(options, offset, flags);
}
+static int ndisc_option_build_flags_extension(const sd_ndisc_option *option, uint8_t **ret) {
+ assert(option);
+ assert(option->type == SD_NDISC_OPTION_FLAGS_EXTENSION);
+ assert(ret);
+
+ _cleanup_free_ uint8_t *buf = new(uint8_t, 8);
+ if (!buf)
+ return 0;
+
+ unaligned_write_be64(buf, (option->extended_flags & UINT64_C(0x00ffffffffffff00)) << 8);
+ buf[0] = SD_NDISC_OPTION_FLAGS_EXTENSION;
+ buf[1] = 1;
+
+ *ret = TAKE_PTR(buf);
+ return 0;
+}
+
int ndisc_option_add_dnssl(Set **options, size_t offset, char * const *domains, usec_t lifetime) {
int r;
return ndisc_option_add_dnssl(options, offset, l, lifetime);
}
+static int ndisc_option_build_dnssl(const sd_ndisc_option *option, uint8_t **ret) {
+ int r;
+
+ assert(option);
+ assert(option->type == SD_NDISC_OPTION_DNSSL);
+ assert(ret);
+
+ size_t len = 8;
+ STRV_FOREACH(s, option->dnssl.domains)
+ len += strlen(*s) + 2;
+ len = DIV_ROUND_UP(len, 8);
+
+ be32_t lifetime = usec_to_be32_sec(option->dnssl.lifetime);
+
+ _cleanup_free_ uint8_t *buf = new(uint8_t, len * 8);
+ if (!buf)
+ return -ENOMEM;
+
+ buf[0] = SD_NDISC_OPTION_DNSSL;
+ buf[1] = len;
+ buf[2] = 0;
+ buf[3] = 0;
+ memcpy(buf + 4, &lifetime, sizeof(be32_t));
+
+ size_t remaining = len * 8 - 8;
+ uint8_t *p = buf + 8;
+
+ STRV_FOREACH(s, option->dnssl.domains) {
+ r = dns_name_to_wire_format(*s, p, remaining, /* canonical = */ false);
+ if (r < 0)
+ return r;
+
+ assert(remaining >= (size_t) r);
+ p += r;
+ remaining -= r;
+ }
+
+ if (remaining > 0)
+ memset(p, 0, remaining);
+
+ *ret = TAKE_PTR(buf);
+ return 0;
+}
+
int ndisc_option_add_captive_portal(Set **options, size_t offset, const char *portal) {
assert(options);
return ndisc_option_add_captive_portal(options, offset, portal);
}
+static int ndisc_option_build_captive_portal(const sd_ndisc_option *option, uint8_t **ret) {
+ assert(option);
+ assert(option->type == SD_NDISC_OPTION_CAPTIVE_PORTAL);
+ assert(ret);
+
+ size_t len_portal = strlen(option->captive_portal);
+ size_t len = DIV_ROUND_UP(len_portal + 1 + 2, 8);
+
+ _cleanup_free_ uint8_t *buf = new(uint8_t, len * 8);
+ if (!buf)
+ return -ENOMEM;
+
+ buf[0] = SD_NDISC_OPTION_CAPTIVE_PORTAL;
+ buf[1] = len;
+
+ uint8_t *p = mempcpy(buf + 2, option->captive_portal, len_portal);
+ size_t remaining = len * 8 - 2 - len_portal;
+ if (remaining > 0)
+ memset(p, 0, remaining);
+
+ *ret = TAKE_PTR(buf);
+ return 0;
+}
+
static const uint8_t prefix_length_code_to_prefix_length[_PREFIX_LENGTH_CODE_MAX] = {
[PREFIX_LENGTH_CODE_96] = 96,
[PREFIX_LENGTH_CODE_64] = 64,
return ndisc_option_add_prefix64(options, offset, prefixlen, &prefix, lifetime);
}
+static int ndisc_option_build_prefix64(const sd_ndisc_option *option, uint8_t **ret) {
+ int r;
+
+ assert(option);
+ assert(option->type == SD_NDISC_OPTION_PREF64);
+ assert(ret);
+
+ uint8_t code;
+ r = pref64_prefix_length_to_plc(option->prefix64.prefixlen, &code);
+ if (r < 0)
+ return r;
+
+ uint16_t lifetime;
+ if (option->prefix64.lifetime >= PREF64_SCALED_LIFETIME_MASK * USEC_PER_SEC)
+ lifetime = PREF64_SCALED_LIFETIME_MASK;
+ else
+ lifetime = (uint16_t) DIV_ROUND_UP(option->prefix64.lifetime, USEC_PER_SEC) & PREF64_SCALED_LIFETIME_MASK;
+
+ _cleanup_free_ uint8_t *buf = new(uint8_t, 2 * 8);
+ if (!buf)
+ return -ENOMEM;
+
+ buf[0] = SD_NDISC_OPTION_PREF64;
+ buf[1] = 2;
+ unaligned_write_be16(buf + 2, lifetime | code);
+ memcpy(buf + 4, &option->prefix64.prefix, 12);
+
+ *ret = TAKE_PTR(buf);
+ return 0;
+}
+
static int ndisc_option_parse_default(Set **options, size_t offset, size_t len, const uint8_t *opt) {
assert(options);
assert(opt);
return log_debug_errno(r, "Failed to parse NDisc option header: %m");
switch (type) {
+ case 0:
+ r = -EBADMSG;
+ break;
+
case SD_NDISC_OPTION_SOURCE_LL_ADDRESS:
case SD_NDISC_OPTION_TARGET_LL_ADDRESS:
r = ndisc_option_parse_link_layer_address(&options, offset, length, opt);
*ret = p->mac;
return 0;
}
+
+int ndisc_send(int fd, const struct sockaddr_in6 *dst, const struct icmp6_hdr *hdr, Set *options) {
+ int r;
+
+ assert(fd >= 0);
+ assert(dst);
+ assert(hdr);
+
+ struct iovec *iov = NULL;
+ size_t n_iov = 0;
+ CLEANUP_ARRAY(iov, n_iov, iovec_array_free);
+
+ iov = new(struct iovec, 1 + set_size(options));
+ if (!iov)
+ return -ENOMEM;
+
+ r = ndisc_header_size(hdr->icmp6_type);
+ if (r < 0)
+ return r;
+ size_t hdr_size = r;
+
+ _cleanup_free_ uint8_t *copy = newdup(uint8_t, hdr, hdr_size);
+ if (!copy)
+ return -ENOMEM;
+
+ iov[n_iov++] = IOVEC_MAKE(TAKE_PTR(copy), hdr_size);
+
+ const sd_ndisc_option *option;
+ SET_FOREACH(option, options) {
+ _cleanup_free_ uint8_t *buf = NULL;
+
+ switch (option->type) {
+ case 0:
+ r = ndisc_option_build_raw(option, &buf);
+ break;
+
+ case SD_NDISC_OPTION_SOURCE_LL_ADDRESS:
+ case SD_NDISC_OPTION_TARGET_LL_ADDRESS:
+ r = ndisc_option_build_link_layer_address(option, &buf);
+ break;
+
+ case SD_NDISC_OPTION_PREFIX_INFORMATION:
+ r = ndisc_option_build_prefix(option, &buf);
+ break;
+
+ case SD_NDISC_OPTION_REDIRECTED_HEADER:
+ r = ndisc_option_build_redirected_header(option, &buf);
+ break;
+
+ case SD_NDISC_OPTION_MTU:
+ r = ndisc_option_build_mtu(option, &buf);
+ break;
+
+ case SD_NDISC_OPTION_ROUTE_INFORMATION:
+ r = ndisc_option_build_route(option, &buf);
+ break;
+
+ case SD_NDISC_OPTION_RDNSS:
+ r = ndisc_option_build_rdnss(option, &buf);
+ break;
+
+ case SD_NDISC_OPTION_FLAGS_EXTENSION:
+ r = ndisc_option_build_flags_extension(option, &buf);
+ break;
+
+ case SD_NDISC_OPTION_DNSSL:
+ r = ndisc_option_build_dnssl(option, &buf);
+ break;
+
+ case SD_NDISC_OPTION_CAPTIVE_PORTAL:
+ r = ndisc_option_build_captive_portal(option, &buf);
+ break;
+
+ case SD_NDISC_OPTION_PREF64:
+ r = ndisc_option_build_prefix64(option, &buf);
+ break;
+
+ default:
+ continue;
+ }
+ if (r == -ENOMEM)
+ return log_oom_debug();
+ if (r < 0)
+ log_debug_errno(r, "Failed to build NDisc option %u, ignoring: %m", option->type);
+
+ iov[n_iov++] = IOVEC_MAKE(buf, buf[1] * 8);
+ TAKE_PTR(buf);
+ }
+
+ return icmp6_send(fd, dst, iov, n_iov);
+}
#include <inttypes.h>
#include <net/ethernet.h>
+#include <netinet/icmp6.h>
#include <netinet/in.h>
#include <netinet/ip6.h>
+#include <sys/uio.h>
#include "sd-ndisc-protocol.h"
#include "set.h"
#include "time-util.h"
+typedef struct sd_ndisc_raw {
+ uint8_t *bytes;
+ size_t length;
+} sd_ndisc_raw;
+
/* Mostly equivalent to struct nd_opt_prefix_info, but using usec_t. */
typedef struct sd_ndisc_prefix {
uint8_t flags;
size_t offset;
union {
+ sd_ndisc_raw raw; /* for testing or unsupported options */
struct ether_addr mac; /* SD_NDISC_OPTION_SOURCE_LL_ADDRESS or SD_NDISC_OPTION_TARGET_LL_ADDRESS */
sd_ndisc_prefix prefix; /* SD_NDISC_OPTION_PREFIX_INFORMATION */
struct ip6_hdr hdr; /* SD_NDISC_OPTION_REDIRECTED_HEADER */
int ndisc_option_get_mac(Set *options, uint8_t type, struct ether_addr *ret);
+int ndisc_option_add_raw(
+ Set **options,
+ size_t offset,
+ size_t length,
+ const uint8_t *bytes);
int ndisc_option_add_link_layer_address(
Set **options,
uint8_t opt,
uint8_t prefixlen,
const struct in6_addr *prefix,
usec_t lifetime);
+
+int ndisc_send(int fd, const struct sockaddr_in6 *dst, const struct icmp6_hdr *hdr, Set *options);
#include "sd-ndisc.h"
#include "alloc-util.h"
+#include "ether-addr-util.h"
#include "event-util.h"
#include "fd-util.h"
#include "icmp6-util.h"
return 0;
}
+static int ndisc_send_router_solicitation(sd_ndisc *nd) {
+ static const struct sockaddr_in6 dst = {
+ .sin6_family = AF_INET6,
+ .sin6_addr = IN6ADDR_ALL_ROUTERS_MULTICAST_INIT,
+ };
+ static const struct nd_router_solicit header = {
+ .nd_rs_type = ND_ROUTER_SOLICIT,
+ };
+
+ _cleanup_set_free_ Set *options = NULL;
+ int r;
+
+ assert(nd);
+
+ if (!ether_addr_is_null(&nd->mac_addr)) {
+ r = ndisc_option_add_link_layer_address(&options, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, 0, &nd->mac_addr);
+ if (r < 0)
+ return r;
+ }
+
+ return ndisc_send(nd->fd, &dst, &header.nd_rs_hdr, options);
+}
+
static usec_t ndisc_timeout_compute_random(usec_t val) {
/* compute a time that is random within ±10% of the given value */
return val - val / 10 +
if (r < 0)
goto fail;
- r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr);
+ r = ndisc_send_router_solicitation(nd);
if (r < 0)
log_ndisc_errno(nd, r, "Failed to send Router Solicitation, next solicitation in %s, ignoring: %m",
FORMAT_TIMESPAN(nd->retransmit_time, USEC_PER_SEC));
#include "alloc-util.h"
#include "fd-util.h"
#include "hexdecoct.h"
+#include "icmp6-packet.h"
#include "icmp6-util-unix.h"
#include "socket-util.h"
#include "strv.h"
};
static bool verbose = false;
-static sd_ndisc *test_timeout_nd;
static void router_dump(sd_ndisc_router *rt) {
struct in6_addr addr;
sd_event_exit(e, 0);
}
+static int on_recv_rs(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ _cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = NULL;
+ assert_se(icmp6_packet_receive(fd, &packet) >= 0);
+
+ return send_ra(0);
+}
+
TEST(rs) {
_cleanup_(sd_event_unrefp) sd_event *e = NULL;
+ _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
_cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL;
- send_ra_function = send_ra;
-
assert_se(sd_event_new(&e) >= 0);
assert_se(sd_ndisc_new(&nd) >= 0);
assert_se(sd_ndisc_start(nd) >= 0);
+ assert_se(sd_event_add_io(e, &s, test_fd[1], EPOLLIN, on_recv_rs, nd) >= 0);
+ assert_se(sd_event_source_set_io_fd_own(s, true) >= 0);
+
assert_se(sd_event_loop(e) >= 0);
- test_fd[1] = safe_close(test_fd[1]);
+ test_fd[1] = -EBADF;
}
static int send_ra_invalid_domain(uint8_t flags) {
return 0;
}
+static int on_recv_rs_invalid_domain(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ _cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = NULL;
+ assert_se(icmp6_packet_receive(fd, &packet) >= 0);
+
+ return send_ra_invalid_domain(0);
+}
+
TEST(invalid_domain) {
_cleanup_(sd_event_unrefp) sd_event *e = NULL;
+ _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
_cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL;
- send_ra_function = send_ra_invalid_domain;
-
assert_se(sd_event_new(&e) >= 0);
assert_se(sd_ndisc_new(&nd) >= 0);
assert_se(sd_ndisc_start(nd) >= 0);
+ assert_se(sd_event_add_io(e, &s, test_fd[1], EPOLLIN, on_recv_rs_invalid_domain, nd) >= 0);
+ assert_se(sd_event_source_set_io_fd_own(s, true) >= 0);
+
assert_se(sd_event_loop(e) >= 0);
- test_fd[1] = safe_close(test_fd[1]);
+ test_fd[1] = -EBADF;
}
-static int test_timeout_value(uint8_t flags) {
+static int on_recv_rs_timeout(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ _cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = NULL;
+ sd_ndisc *nd = ASSERT_PTR(userdata);
static int count = 0;
static usec_t last = 0;
- sd_ndisc *nd = test_timeout_nd;
usec_t min, max;
- assert_se(nd);
- assert_se(nd->event);
+ assert_se(icmp6_packet_receive(fd, &packet) >= 0);
if (++count >= 20)
sd_event_exit(nd->event, 0);
TEST(timeout) {
_cleanup_(sd_event_unrefp) sd_event *e = NULL;
+ _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
_cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL;
- send_ra_function = test_timeout_value;
-
assert_se(sd_event_new(&e) >= 0);
assert_se(sd_ndisc_new(&nd) >= 0);
assert_se(nd);
- test_timeout_nd = nd;
-
assert_se(sd_ndisc_attach_event(nd, e, 0) >= 0);
assert_se(sd_ndisc_set_ifindex(nd, 42) >= 0);
assert_se(sd_ndisc_start(nd) >= 0);
+ assert_se(sd_event_add_io(e, &s, test_fd[1], EPOLLIN, on_recv_rs_timeout, nd) >= 0);
+ assert_se(sd_event_source_set_io_fd_own(s, true) >= 0);
+
assert_se(sd_event_loop(e) >= 0);
- test_fd[1] = safe_close(test_fd[1]);
+ test_fd[1] = -EBADF;
}
DEFINE_TEST_MAIN(LOG_DEBUG);
Manager *manager,
const char *scope,
const PidRef *pidref,
+ bool allow_pidfd,
const char *slice,
const char *description,
const char * const *requires,
if (r < 0)
return r;
- r = bus_append_scope_pidref(m, pidref);
+ r = bus_append_scope_pidref(m, pidref, allow_pidfd);
if (r < 0)
return r;
return r;
r = sd_bus_call(manager->bus, m, 0, error, &reply);
- if (r < 0)
+ if (r < 0) {
+ /* If this failed with a property we couldn't write, this is quite likely because the server
+ * doesn't support PIDFDs yet, let's try without. */
+ if (allow_pidfd &&
+ sd_bus_error_has_names(error, SD_BUS_ERROR_UNKNOWN_PROPERTY, SD_BUS_ERROR_PROPERTY_READ_ONLY))
+ return manager_start_scope(
+ manager,
+ scope,
+ pidref,
+ /* allow_pidfd = */ false,
+ slice,
+ description,
+ requires,
+ extra_after,
+ requires_mounts_for,
+ more_properties,
+ error,
+ ret_job);
+
return r;
+ }
return strdup_job(reply, ret_job);
}
Manager *manager,
const char *scope,
const PidRef *pidref,
+ bool allow_pidfd,
const char *slice,
const char *description,
const char * const *requires,
s->manager,
scope,
&s->leader,
+ /* allow_pidfd = */ true,
s->user->slice,
description,
/* These should have been pulled in explicitly in user_start(). Just to be sure. */
if (r < 0)
return r;
- r = bus_append_scope_pidref(m, &machine->leader);
+ r = bus_append_scope_pidref(m, &machine->leader, /* allow_pidfd = */ true);
if (r < 0)
return r;
assert(address);
assert(ret);
+ r = address_acquire_from_dhcp_server_leases_file(link, address, ret);
+ if (r != -ENOENT)
+ return r;
+
r = address_pool_acquire(link->manager, address->family, address->prefixlen, &a);
if (r < 0)
return r;
bool scope_set:1;
bool ip_masquerade_done:1;
bool requested_as_null:1;
+ bool used_by_dhcp_server:1;
/* duplicate_address_detection is only used by static or IPv4 dynamic addresses.
* To control DAD for IPv6 dynamic addresses, set IFA_F_NODAD to flags. */
#include "sd-dhcp-server.h"
#include "dhcp-protocol.h"
+#include "dhcp-server-lease-internal.h"
#include "fd-util.h"
#include "fileio.h"
#include "network-common.h"
/* TODO: check if the prefix length is small enough for the pool. */
network->dhcp_server_address = address;
+ address->used_by_dhcp_server = true;
break;
}
if (!network->dhcp_server_address) {
a->prefixlen = network->dhcp_server_address_prefixlen;
a->in_addr.in = network->dhcp_server_address_in_addr;
a->requested_as_null = !in4_addr_is_set(&network->dhcp_server_address_in_addr);
+ a->used_by_dhcp_server = true;
r = address_section_verify(a);
if (r < 0)
return 0;
}
+int address_acquire_from_dhcp_server_leases_file(Link *link, const Address *address, union in_addr_union *ret) {
+ struct in_addr a;
+ uint8_t prefixlen;
+ int r;
+
+ assert(link);
+ assert(link->manager);
+ assert(address);
+ assert(ret);
+
+ /* If the DHCP server address is configured as a null address, reuse the server address of the
+ * previous instance. */
+ if (address->family != AF_INET)
+ return -ENOENT;
+
+ if (!address->used_by_dhcp_server)
+ return -ENOENT;
+
+ if (!link_dhcp4_server_enabled(link))
+ return -ENOENT;
+
+ if (link->manager->persistent_storage_fd < 0)
+ return -EBUSY; /* The persistent storage is not ready, try later again. */
+
+ _cleanup_free_ char *lease_file = path_join("dhcp-server-lease", link->ifname);
+ if (!lease_file)
+ return -ENOMEM;
+
+ r = dhcp_server_leases_file_get_server_address(
+ link->manager->persistent_storage_fd,
+ lease_file,
+ &a,
+ &prefixlen);
+ if (r < 0)
+ return r;
+
+ if (prefixlen != address->prefixlen)
+ return -ENOENT;
+
+ ret->in = a;
+ return 0;
+}
+
int link_start_dhcp4_server(Link *link) {
int r;
#pragma once
#include "conf-parser.h"
+#include "in-addr-util.h"
#include "set.h"
+typedef struct Address Address;
typedef struct Link Link;
typedef struct Manager Manager;
typedef struct Network Network;
int network_adjust_dhcp_server(Network *network, Set **addresses);
-
+int address_acquire_from_dhcp_server_leases_file(Link *link, const Address *address, union in_addr_union *ret);
int link_request_dhcp_server(Link *link);
int link_start_dhcp4_server(Link *link);
description = strjoina("Container ", machine_name);
- if (allow_pidfd) {
- _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
- r = pidref_set_pid(&pidref, pid);
- if (r < 0)
- return log_error_errno(r, "Failed to allocate PID reference: %m");
+ _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
+ r = pidref_set_pid(&pidref, pid);
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocate PID reference: %m");
- r = bus_append_scope_pidref(m, &pidref);
- } else
- r = sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, (uint32_t) pid);
+ r = bus_append_scope_pidref(m, &pidref, allow_pidfd);
if (r < 0)
return bus_log_create_error(r);
if (dissected_image) {
/* Now we know the uid shift, let's now mount everything else that might be in the image. */
- r = dissected_image_mount(
+ r = dissected_image_mount_and_warn(
dissected_image,
directory,
arg_uid_shift,
determine_dissect_image_flags()|
DISSECT_IMAGE_MOUNT_NON_ROOT_ONLY|
(idmap ? DISSECT_IMAGE_MOUNT_IDMAPPED : 0));
- if (r == -EUCLEAN)
- return log_error_errno(r, "File system check for image failed: %m");
if (r < 0)
- return log_error_errno(r, "Failed to mount image file system: %m");
+ return r;
}
if (arg_unified_cgroup_hierarchy == CGROUP_UNIFIED_UNKNOWN) {
if (r < 0)
return r;
- if (allow_pidfd) {
- _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
+ _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
- r = pidref_set_self(&pidref);
- if (r < 0)
- return r;
+ r = pidref_set_self(&pidref);
+ if (r < 0)
+ return r;
- r = bus_append_scope_pidref(m, &pidref);
- } else
- r = sd_bus_message_append(
- m, "(sv)",
- "PIDs", "au", 1, getpid_cached());
+ r = bus_append_scope_pidref(m, &pidref, allow_pidfd);
if (r < 0)
return bus_log_create_error(r);
return 0;
}
-int bus_append_scope_pidref(sd_bus_message *m, const PidRef *pidref) {
+int bus_append_scope_pidref(sd_bus_message *m, const PidRef *pidref, bool allow_pidfd) {
assert(m);
if (!pidref_is_set(pidref))
return -ESRCH;
- if (pidref->fd >= 0)
+ if (pidref->fd >= 0 && allow_pidfd)
return sd_bus_message_append(
m, "(sv)",
"PIDFDs", "ah", 1, pidref->fd);
int bus_append_unit_property_assignment(sd_bus_message *m, UnitType t, const char *assignment);
int bus_append_unit_property_assignment_many(sd_bus_message *m, UnitType t, char **l);
-int bus_append_scope_pidref(sd_bus_message *m, const PidRef *pidref);
+int bus_append_scope_pidref(sd_bus_message *m, const PidRef *pidref, bool allow_pidfd);
int bus_deserialize_and_dump_unit_file_changes(sd_bus_message *m, bool quiet);
DLSYM_FUNCTION(crypt_keyslot_max);
DLSYM_FUNCTION(crypt_load);
DLSYM_FUNCTION(crypt_resize);
-DLSYM_FUNCTION(crypt_resume_by_passphrase);
+#if HAVE_CRYPT_RESUME_BY_VOLUME_KEY
+DLSYM_FUNCTION(crypt_resume_by_volume_key);
+#endif
DLSYM_FUNCTION(crypt_set_data_device);
DLSYM_FUNCTION(crypt_set_debug_level);
DLSYM_FUNCTION(crypt_set_log_callback);
DLSYM_ARG(crypt_keyslot_max),
DLSYM_ARG(crypt_load),
DLSYM_ARG(crypt_resize),
- DLSYM_ARG(crypt_resume_by_passphrase),
+#if HAVE_CRYPT_RESUME_BY_VOLUME_KEY
+ DLSYM_ARG(crypt_resume_by_volume_key),
+#endif
DLSYM_ARG(crypt_set_data_device),
DLSYM_ARG(crypt_set_debug_level),
DLSYM_ARG(crypt_set_log_callback),
DLSYM_PROTOTYPE(crypt_keyslot_max);
DLSYM_PROTOTYPE(crypt_load);
DLSYM_PROTOTYPE(crypt_resize);
-DLSYM_PROTOTYPE(crypt_resume_by_passphrase);
+#if HAVE_CRYPT_RESUME_BY_VOLUME_KEY
+DLSYM_PROTOTYPE(crypt_resume_by_volume_key);
+#endif
DLSYM_PROTOTYPE(crypt_set_data_device);
DLSYM_PROTOTYPE(crypt_set_debug_level);
DLSYM_PROTOTYPE(crypt_set_log_callback);
r = dissected_image_mount(m, where, uid_shift, uid_range, userns_fd, flags);
if (r == -ENXIO)
- return log_error_errno(r, "Not root file system found in image.");
+ return log_error_errno(r, "Failed to mount image: No root file system found in image.");
if (r == -EMEDIUMTYPE)
- return log_error_errno(r, "No suitable os-release/extension-release file in image found.");
+ return log_error_errno(r, "Failed to mount image: No suitable os-release/extension-release file in image found.");
if (r == -EUNATCH)
- return log_error_errno(r, "Encrypted file system discovered, but decryption not requested.");
+ return log_error_errno(r, "Failed to mount image: Encrypted file system discovered, but decryption not requested.");
if (r == -EUCLEAN)
- return log_error_errno(r, "File system check on image failed.");
+ return log_error_errno(r, "Failed to mount image: File system check on image failed.");
if (r == -EBUSY)
- return log_error_errno(r, "File system already mounted elsewhere.");
+ return log_error_errno(r, "Failed to mount image: File system already mounted elsewhere.");
if (r == -EAFNOSUPPORT)
- return log_error_errno(r, "File system type not supported or not known.");
+ return log_error_errno(r, "Failed to mount image: File system type not supported or not known.");
if (r == -EIDRM)
- return log_error_errno(r, "File system is too uncommon, refused.");
+ return log_error_errno(r, "Failed to mount image: File system is too uncommon, refused.");
if (r < 0)
return log_error_errno(r, "Failed to mount image: %m");
if (r < 0)
return r;
- return path_equal_ptr(parent, lp->generator) ||
- path_equal_ptr(parent, lp->generator_early) ||
- path_equal_ptr(parent, lp->generator_late);
+ return PATH_IN_SET(parent,
+ lp->generator,
+ lp->generator_early,
+ lp->generator_late);
}
static int path_is_transient(const LookupPaths *lp, const char *path) {
if (r < 0)
return r;
- return path_equal_ptr(parent, lp->transient);
+ return path_equal(parent, lp->transient);
}
static int path_is_control(const LookupPaths *lp, const char *path) {
if (r < 0)
return r;
- return path_equal_ptr(parent, lp->persistent_control) ||
- path_equal_ptr(parent, lp->runtime_control);
+ return PATH_IN_SET(parent,
+ lp->persistent_control,
+ lp->runtime_control);
}
static int path_is_config(const LookupPaths *lp, const char *path, bool check_parent) {
path = parent;
}
- return path_equal_ptr(path, lp->persistent_config) ||
- path_equal_ptr(path, lp->runtime_config);
+ return PATH_IN_SET(path,
+ lp->persistent_config,
+ lp->runtime_config);
}
static int path_is_runtime(const LookupPaths *lp, const char *path, bool check_parent) {
path = parent;
}
- return path_equal_ptr(path, lp->runtime_config) ||
- path_equal_ptr(path, lp->generator) ||
- path_equal_ptr(path, lp->generator_early) ||
- path_equal_ptr(path, lp->generator_late) ||
- path_equal_ptr(path, lp->transient) ||
- path_equal_ptr(path, lp->runtime_control);
+ return PATH_IN_SET(path,
+ lp->runtime_config,
+ lp->generator,
+ lp->generator_early,
+ lp->generator_late,
+ lp->transient,
+ lp->runtime_control);
}
static int path_is_vendor_or_generator(const LookupPaths *lp, const char *path) {
if (r > 0) {
/* We found symlinks in this dir? Yay! Let's see where precisely it is enabled. */
- if (path_equal_ptr(*p, lp->persistent_config)) {
+ if (path_equal(*p, lp->persistent_config)) {
/* This is the best outcome, let's return it immediately. */
*state = UNIT_FILE_ENABLED;
return 1;
enabled_at_all = true;
} else if (same_name_link) {
- if (path_equal_ptr(*p, lp->persistent_config))
+ if (path_equal(*p, lp->persistent_config))
same_name_link_config = true;
else {
r = path_is_runtime(lp, *p, false);
} \
})
+#define ASSERT_TRUE(expr) \
+ ({ \
+ if (!(expr)) { \
+ log_error("%s:%i: Assertion failed: expected \"%s\" to be true", \
+ PROJECT_FILE, __LINE__, #expr); \
+ abort(); \
+ } \
+ })
+
+#define ASSERT_FALSE(expr) \
+ ({ \
+ if ((expr)) { \
+ log_error("%s:%i: Assertion failed: expected \"%s\" to be false", \
+ PROJECT_FILE, __LINE__, #expr); \
+ abort(); \
+ } \
+ })
+
+#define ASSERT_NULL(expr) \
+ ({ \
+ if ((expr) != NULL) { \
+ log_error("%s:%i: Assertion failed: expected \"%s\" to be NULL", \
+ PROJECT_FILE, __LINE__, #expr); \
+ abort(); \
+ } \
+ })
+
+#define ASSERT_NOT_NULL(expr) \
+ ({ \
+ if ((expr) == NULL) { \
+ log_error("%s:%i: Assertion failed: expected \"%s\" to be not NULL", \
+ PROJECT_FILE, __LINE__, #expr); \
+ abort(); \
+ } \
+ })
+
+#define ASSERT_STREQ(expr1, expr2) \
+ ({ \
+ const char* _expr1 = (expr1); \
+ const char* _expr2 = (expr2); \
+ if (strcmp(_expr1, _expr2) != 0) { \
+ log_error("%s:%i: Assertion failed: expected \"%s == %s\", but \"%s != %s\"", \
+ PROJECT_FILE, __LINE__, #expr1, #expr2, _expr1, _expr2); \
+ abort(); \
+ } \
+ })
+
/* DECIMAL_STR_FMT() uses _Generic which cannot be used in string concatenation so we have to format the
* input into strings first and then format those into the final assertion message. */
-#define ASSERT_EQ(expr1, expr2) \
- ({ \
- typeof(expr1) _expr1 = (expr1); \
- typeof(expr2) _expr2 = (expr2); \
- if (_expr1 != _expr2) { \
- char _sexpr1[DECIMAL_STR_MAX(typeof(expr1))]; \
- char _sexpr2[DECIMAL_STR_MAX(typeof(expr2))]; \
- xsprintf(_sexpr1, DECIMAL_STR_FMT(_expr1), _expr1); \
- xsprintf(_sexpr2, DECIMAL_STR_FMT(_expr2), _expr2); \
- log_error("%s:%i: Assertion failed: expected \"%s == %s\", but \"%s != %s\"", \
- PROJECT_FILE, __LINE__, #expr1, #expr2, _sexpr1, _sexpr2); \
- abort(); \
- } \
+#define ASSERT_EQ(expr1, expr2) \
+ ({ \
+ typeof(expr1) _expr1 = (expr1); \
+ typeof(expr2) _expr2 = (expr2); \
+ if (_expr1 != _expr2) { \
+ char _sexpr1[DECIMAL_STR_MAX(typeof(expr1))]; \
+ char _sexpr2[DECIMAL_STR_MAX(typeof(expr2))]; \
+ xsprintf(_sexpr1, DECIMAL_STR_FMT(_expr1), _expr1); \
+ xsprintf(_sexpr2, DECIMAL_STR_FMT(_expr2), _expr2); \
+ log_error("%s:%i: Assertion failed: expected \"%s == %s\", but \"%s != %s\"", \
+ PROJECT_FILE, __LINE__, #expr1, #expr2, _sexpr1, _sexpr2); \
+ abort(); \
+ } \
+ })
+
+#define ASSERT_GE(expr1, expr2) \
+ ({ \
+ typeof(expr1) _expr1 = (expr1); \
+ typeof(expr2) _expr2 = (expr2); \
+ if (_expr1 < _expr2) { \
+ char _sexpr1[DECIMAL_STR_MAX(typeof(expr1))]; \
+ char _sexpr2[DECIMAL_STR_MAX(typeof(expr2))]; \
+ xsprintf(_sexpr1, DECIMAL_STR_FMT(_expr1), _expr1); \
+ xsprintf(_sexpr2, DECIMAL_STR_FMT(_expr2), _expr2); \
+ log_error("%s:%i: Assertion failed: expected \"%s >= %s\", but \"%s < %s\"", \
+ PROJECT_FILE, __LINE__, #expr1, #expr2, _sexpr1, _sexpr2); \
+ abort(); \
+ } \
+ })
+
+#define ASSERT_LE(expr1, expr2) \
+ ({ \
+ typeof(expr1) _expr1 = (expr1); \
+ typeof(expr2) _expr2 = (expr2); \
+ if (_expr1 > _expr2) { \
+ char _sexpr1[DECIMAL_STR_MAX(typeof(expr1))]; \
+ char _sexpr2[DECIMAL_STR_MAX(typeof(expr2))]; \
+ xsprintf(_sexpr1, DECIMAL_STR_FMT(_expr1), _expr1); \
+ xsprintf(_sexpr2, DECIMAL_STR_FMT(_expr2), _expr2); \
+ log_error("%s:%i: Assertion failed: expected \"%s <= %s\", but \"%s > %s\"", \
+ PROJECT_FILE, __LINE__, #expr1, #expr2, _sexpr1, _sexpr2); \
+ abort(); \
+ } \
+ })
+
+#define ASSERT_NE(expr1, expr2) \
+ ({ \
+ typeof(expr1) _expr1 = (expr1); \
+ typeof(expr2) _expr2 = (expr2); \
+ if (_expr1 == _expr2) { \
+ char _sexpr1[DECIMAL_STR_MAX(typeof(expr1))]; \
+ char _sexpr2[DECIMAL_STR_MAX(typeof(expr2))]; \
+ xsprintf(_sexpr1, DECIMAL_STR_FMT(_expr1), _expr1); \
+ xsprintf(_sexpr2, DECIMAL_STR_FMT(_expr2), _expr2); \
+ log_error("%s:%i: Assertion failed: expected \"%s != %s\", but \"%s == %s\"", \
+ PROJECT_FILE, __LINE__, #expr1, #expr2, _sexpr1, _sexpr2); \
+ abort(); \
+ } \
})
-#define ASSERT_GE(expr1, expr2) \
- ({ \
- typeof(expr1) _expr1 = (expr1); \
- typeof(expr2) _expr2 = (expr2); \
- if (_expr1 < _expr2) { \
- char _sexpr1[DECIMAL_STR_MAX(typeof(expr1))]; \
- char _sexpr2[DECIMAL_STR_MAX(typeof(expr2))]; \
- xsprintf(_sexpr1, DECIMAL_STR_FMT(_expr1), _expr1); \
- xsprintf(_sexpr2, DECIMAL_STR_FMT(_expr2), _expr2); \
- log_error("%s:%i: Assertion failed: expected \"%s >= %s\", but \"%s < %s\"", \
- PROJECT_FILE, __LINE__, #expr1, #expr2, _sexpr1, _sexpr2); \
- abort(); \
- } \
+#define ASSERT_GT(expr1, expr2) \
+ ({ \
+ typeof(expr1) _expr1 = (expr1); \
+ typeof(expr2) _expr2 = (expr2); \
+ if (!(_expr1 > _expr2)) { \
+ char _sexpr1[DECIMAL_STR_MAX(typeof(expr1))]; \
+ char _sexpr2[DECIMAL_STR_MAX(typeof(expr2))]; \
+ xsprintf(_sexpr1, DECIMAL_STR_FMT(_expr1), _expr1); \
+ xsprintf(_sexpr2, DECIMAL_STR_FMT(_expr2), _expr2); \
+ log_error("%s:%i: Assertion failed: expected \"%s > %s\", but \"%s <= %s\"", \
+ PROJECT_FILE, __LINE__, #expr1, #expr2, _sexpr1, _sexpr2); \
+ abort(); \
+ } \
})
-#define ASSERT_LE(expr1, expr2) \
- ({ \
- typeof(expr1) _expr1 = (expr1); \
- typeof(expr2) _expr2 = (expr2); \
- if (_expr1 > _expr2) { \
- char _sexpr1[DECIMAL_STR_MAX(typeof(expr1))]; \
- char _sexpr2[DECIMAL_STR_MAX(typeof(expr2))]; \
- xsprintf(_sexpr1, DECIMAL_STR_FMT(_expr1), _expr1); \
- xsprintf(_sexpr2, DECIMAL_STR_FMT(_expr2), _expr2); \
- log_error("%s:%i: Assertion failed: expected \"%s <= %s\", but \"%s > %s\"", \
- PROJECT_FILE, __LINE__, #expr1, #expr2, _sexpr1, _sexpr2); \
- abort(); \
- } \
+#define ASSERT_LT(expr1, expr2) \
+ ({ \
+ typeof(expr1) _expr1 = (expr1); \
+ typeof(expr2) _expr2 = (expr2); \
+ if (!(_expr1 < _expr2)) { \
+ char _sexpr1[DECIMAL_STR_MAX(typeof(expr1))]; \
+ char _sexpr2[DECIMAL_STR_MAX(typeof(expr2))]; \
+ xsprintf(_sexpr1, DECIMAL_STR_FMT(_expr1), _expr1); \
+ xsprintf(_sexpr2, DECIMAL_STR_FMT(_expr2), _expr2); \
+ log_error("%s:%i: Assertion failed: expected \"%s < %s\", but \"%s >= %s\"", \
+ PROJECT_FILE, __LINE__, #expr1, #expr2, _sexpr1, _sexpr2); \
+ abort(); \
+ } \
})
if (r < 0)
return r;
- _cleanup_(tpm2_handle_freep) Tpm2Handle *encryption_session = NULL;
- r = tpm2_make_encryption_session(c, primary_handle, hmac_key, &encryption_session);
- if (r < 0)
- return r;
-
_cleanup_(Esys_Freep) TPM2B_SENSITIVE_DATA* unsealed = NULL;
for (unsigned i = RETRY_UNSEAL_MAX;; i--) {
+ _cleanup_(tpm2_handle_freep) Tpm2Handle *encryption_session = NULL;
+ r = tpm2_make_encryption_session(c, primary_handle, hmac_key, &encryption_session);
+ if (r < 0)
+ return r;
+
_cleanup_(tpm2_handle_freep) Tpm2Handle *policy_session = NULL;
_cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL;
r = tpm2_make_policy_session(
"Description=OpenSSH Per-Connection Server Daemon\n"
"Documentation=man:systemd-ssh-generator(8) man:sshd(8)\n"
"[Service]\n"
- "ExecStart=-%s -i\n"
- "StandardInput=socket",
+ "ExecStart=-%s -i -o \"AuthorizedKeysFile ${CREDENTIALS_DIRECTORY}/ssh.ephemeral-authorized_keys-all .ssh/authorized_keys\"\n"
+ "StandardInput=socket\n"
+ "ImportCredential=ssh.ephemeral-authorized_keys-all",
sshd_binary);
r = fflush_and_check(f);
const char *a_shell = pick_shell(a),
*b_shell = pick_shell(b);
- if (!path_equal_ptr(a_shell, b_shell) &&
+ if (!path_equal(a_shell, b_shell) &&
!(is_nologin_shell(a_shell) && is_nologin_shell(b_shell))) {
_cleanup_free_ char *pa = NULL, *pb = NULL;
}
TEST(string_compare_func) {
- assert_se(string_compare_func("fred", "wilma") != 0);
+ ASSERT_NE(string_compare_func("fred", "wilma"), 0);
assert_se(string_compare_func("fred", "fred") == 0);
}
"",
result);
hex_result = hexmem(result, sizeof(result));
- assert_se(streq_ptr(hex_result, "cadd5e42114351181f3abff477641d88efb57d2b5641a1e5c6d623363a6d3bad"));
+ ASSERT_STREQ(hex_result, "cadd5e42114351181f3abff477641d88efb57d2b5641a1e5c6d623363a6d3bad");
hex_result = mfree(hex_result);
hmac_sha256_by_string("waldo",
*/
/* file mountpoints */
- assert_se(mkdtemp(tmp_dir) != NULL);
+ ASSERT_NOT_NULL(mkdtemp(tmp_dir));
file1 = path_join(tmp_dir, "file1");
assert_se(file1);
file2 = path_join(tmp_dir, "file2");
#include "tmpfile-util.h"
TEST(path_is_os_tree) {
- assert_se(path_is_os_tree("/") > 0);
- assert_se(path_is_os_tree("/etc") == 0);
+ ASSERT_GT(path_is_os_tree("/"), 0);
+ ASSERT_EQ(path_is_os_tree("/etc"), 0);
assert_se(path_is_os_tree("/idontexist") == -ENOENT);
}
TEST(parse_os_release) {
/* Let's assume that we're running in a valid system, so os-release is available */
_cleanup_free_ char *id = NULL, *id2 = NULL, *name = NULL, *foobar = NULL;
- assert_se(parse_os_release(NULL, "ID", &id) == 0);
+ ASSERT_EQ(parse_os_release(NULL, "ID", &id), 0);
log_info("ID: %s", id);
- assert_se(setenv("SYSTEMD_OS_RELEASE", "/dev/null", 1) == 0);
- assert_se(parse_os_release(NULL, "ID", &id2) == 0);
+ ASSERT_EQ(setenv("SYSTEMD_OS_RELEASE", "/dev/null", 1), 0);
+ ASSERT_EQ(parse_os_release(NULL, "ID", &id2), 0);
log_info("ID: %s", strnull(id2));
_cleanup_(unlink_tempfilep) char tmpfile[] = "/tmp/test-os-util.XXXXXX";
- assert_se(write_tmpfile(tmpfile,
+ ASSERT_EQ(write_tmpfile(tmpfile,
"ID=the-id \n"
- "NAME=the-name") == 0);
+ "NAME=the-name"), 0);
- assert_se(setenv("SYSTEMD_OS_RELEASE", tmpfile, 1) == 0);
- assert_se(parse_os_release(NULL, "ID", &id, "NAME", &name) == 0);
+ ASSERT_EQ(setenv("SYSTEMD_OS_RELEASE", tmpfile, 1), 0);
+ ASSERT_EQ(parse_os_release(NULL, "ID", &id, "NAME", &name), 0);
log_info("ID: %s NAME: %s", id, name);
assert_se(streq(id, "the-id"));
assert_se(streq(name, "the-name"));
_cleanup_(unlink_tempfilep) char tmpfile2[] = "/tmp/test-os-util.XXXXXX";
- assert_se(write_tmpfile(tmpfile2,
+ ASSERT_EQ(write_tmpfile(tmpfile2,
"ID=\"ignored\" \n"
"ID=\"the-id\" \n"
- "NAME='the-name'") == 0);
+ "NAME='the-name'"), 0);
- assert_se(setenv("SYSTEMD_OS_RELEASE", tmpfile2, 1) == 0);
- assert_se(parse_os_release(NULL, "ID", &id, "NAME", &name) == 0);
+ ASSERT_EQ(setenv("SYSTEMD_OS_RELEASE", tmpfile2, 1), 0);
+ ASSERT_EQ(parse_os_release(NULL, "ID", &id, "NAME", &name), 0);
log_info("ID: %s NAME: %s", id, name);
assert_se(streq(id, "the-id"));
assert_se(streq(name, "the-name"));
- assert_se(parse_os_release(NULL, "FOOBAR", &foobar) == 0);
+ ASSERT_EQ(parse_os_release(NULL, "FOOBAR", &foobar), 0);
log_info("FOOBAR: %s", strnull(foobar));
- assert_se(foobar == NULL);
+ ASSERT_NULL(foobar);
assert_se(unsetenv("SYSTEMD_OS_RELEASE") == 0);
}
assert_se(a = path_join(tempdir, "/usr/lib/extension-release.d/extension-release.test"));
assert_se(mkdir_parents(a, 0777) >= 0);
+ ASSERT_GE(mkdir_parents(a, 0777), 0);
r = write_string_file(a, "ID=the-id \n VERSION_ID=the-version-id", WRITE_STRING_FILE_CREATE);
if (r < 0)
if (r < 0)
log_error_errno(r, "Failed to write file: %m");
- assert_se(parse_extension_release(tempdir, IMAGE_CONFEXT, "tester", false, "ID", &id, "VERSION_ID", &version_id) == 0);
+ ASSERT_EQ(parse_extension_release(tempdir, IMAGE_CONFEXT, "tester", false, "ID", &id, "VERSION_ID", &version_id), 0);
log_info("ID: %s VERSION_ID: %s", id, version_id);
assert_se(streq(id, "the-id"));
assert_se(streq(version_id, "the-version-id"));
assert_se(parse_extension_release(tempdir, IMAGE_CONFEXT, "tester", false, "FOOBAR", &foobar) == 0);
log_info("FOOBAR: %s", strnull(foobar));
- assert_se(foobar == NULL);
+ ASSERT_NULL(foobar);
assert_se(parse_extension_release(tempdir, IMAGE_SYSEXT, "test", false, "FOOBAR", &foobar) == 0);
log_info("FOOBAR: %s", strnull(foobar));
- assert_se(foobar == NULL);
+ ASSERT_NULL(foobar);
}
TEST(load_os_release_pairs) {
_cleanup_(unlink_tempfilep) char tmpfile[] = "/tmp/test-os-util.XXXXXX";
- assert_se(write_tmpfile(tmpfile,
+ ASSERT_EQ(write_tmpfile(tmpfile,
"ID=\"ignored\" \n"
"ID=\"the-id\" \n"
- "NAME='the-name'") == 0);
+ "NAME='the-name'"), 0);
- assert_se(setenv("SYSTEMD_OS_RELEASE", tmpfile, 1) == 0);
+ ASSERT_EQ(setenv("SYSTEMD_OS_RELEASE", tmpfile, 1), 0);
_cleanup_strv_free_ char **pairs = NULL;
- assert_se(load_os_release_pairs(NULL, &pairs) == 0);
+ ASSERT_EQ(load_os_release_pairs(NULL, &pairs), 0);
assert_se(strv_equal(pairs, STRV_MAKE("ID", "the-id",
"NAME", "the-name")));
- assert_se(unsetenv("SYSTEMD_OS_RELEASE") == 0);
+ ASSERT_EQ(unsetenv("SYSTEMD_OS_RELEASE"), 0);
}
TEST(os_release_support_ended) {
int r;
- assert_se(os_release_support_ended("1999-01-01", false, NULL) == true);
- assert_se(os_release_support_ended("2037-12-31", false, NULL) == false);
+ ASSERT_TRUE(os_release_support_ended("1999-01-01", false, NULL));
+ ASSERT_FALSE(os_release_support_ended("2037-12-31", false, NULL));
assert_se(os_release_support_ended("-1-1-1", true, NULL) == -EINVAL);
r = os_release_support_ended(NULL, false, NULL);
}
TEST(path) {
- assert_se(path_is_absolute("/"));
+ assert_se( path_is_absolute("/"));
assert_se(!path_is_absolute("./"));
assert_se(streq(basename("./aa/bb/../file.da."), "file.da."));
assert_se(streq(basename("/aa///file..."), "file..."));
assert_se(streq(basename("file.../"), ""));
- assert_se(PATH_IN_SET("/bin", "/", "/bin", "/foo"));
- assert_se(PATH_IN_SET("/bin", "/bin"));
- assert_se(PATH_IN_SET("/bin", "/foo/bar", "/bin"));
- assert_se(PATH_IN_SET("/", "/", "/", "/foo/bar"));
+ assert_se( PATH_IN_SET("/bin", "/", "/bin", "/foo"));
+ assert_se( PATH_IN_SET("/bin", "/bin"));
+ assert_se( PATH_IN_SET("/bin", "/foo/bar", "/bin"));
+ assert_se( PATH_IN_SET("/", "/", "/", "/foo/bar"));
assert_se(!PATH_IN_SET("/", "/abc", "/def"));
- assert_se(path_equal_ptr(NULL, NULL));
- assert_se(path_equal_ptr("/a", "/a"));
- assert_se(!path_equal_ptr("/a", "/b"));
- assert_se(!path_equal_ptr("/a", NULL));
- assert_se(!path_equal_ptr(NULL, "/a"));
+ assert_se( path_equal(NULL, NULL));
+ assert_se( path_equal("/a", "/a"));
+ assert_se(!path_equal("/a", "/b"));
+ assert_se(!path_equal("/a", NULL));
+ assert_se(!path_equal(NULL, "/a"));
+ assert_se(!path_equal("a", NULL));
+ assert_se(!path_equal(NULL, "a"));
}
TEST(is_path) {
log_error("---%s---", s);
assert_se(streq(s, values[i++]));
}
- assert_se(values[i] == NULL);
+ ASSERT_NULL(values[i]);
i = 1;
PATH_FOREACH_PREFIX(s, "/a/b/c/d") {
log_error("---%s---", s);
assert_se(streq(s, values[i++]));
}
- assert_se(values[i] == NULL);
+ ASSERT_NULL(values[i]);
i = 0;
PATH_FOREACH_PREFIX_MORE(s, "////a////b////c///d///////")
assert_se(streq(s, values[i++]));
- assert_se(values[i] == NULL);
+ ASSERT_NULL(values[i]);
i = 1;
PATH_FOREACH_PREFIX(s, "////a////b////c///d///////")
assert_se(streq(s, values[i++]));
- assert_se(values[i] == NULL);
+ ASSERT_NULL(values[i]);
PATH_FOREACH_PREFIX(s, "////")
assert_not_reached();
const char *t;
assert_se(s = path_join(r, p));
- assert_se(path_equal_ptr(s, expected));
+ assert_se(path_equal(s, expected));
t = prefix_roota(r, p);
assert_se(t);
- assert_se(path_equal_ptr(t, expected));
+ assert_se(path_equal(t, expected));
}
TEST(prefix_root) {
memset(buf, 0xff, sizeof(buf));
draw_cylon(buf, sizeof(buf), CYLON_WIDTH, pos);
- assert_se(strlen(buf) < sizeof(buf));
+ ASSERT_LE(strlen(buf), sizeof(buf));
}
TEST(draw_cylon) {
/* Check for successful recovery-key creation */
r = make_recovery_key(&recovery_key);
assert_se(r == 0);
- assert_se(recovery_key != NULL);
+ ASSERT_NOT_NULL(recovery_key);
/* Check that length of formatted key is 72 with 64 modhex characters */
length = strlen(recovery_key);
}
TEST(uid_ptr) {
- assert_se(UID_TO_PTR(0) != NULL);
- assert_se(UID_TO_PTR(1000) != NULL);
+ ASSERT_NOT_NULL(UID_TO_PTR(0));
+ ASSERT_NOT_NULL(UID_TO_PTR(1000));
assert_se(PTR_TO_UID(UID_TO_PTR(0)) == 0);
assert_se(PTR_TO_UID(UID_TO_PTR(1000)) == 1000);
"AddRef", "b", 1,
"CollectMode", "s", "inactive-or-failed");
- if (allow_pidfd) {
- _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
- r = pidref_set_pid(&pidref, getpid_cached());
- if (r < 0)
- return log_error_errno(r, "Failed to allocate PID reference: %m");
+ _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
+ r = pidref_set_self(&pidref);
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocate PID reference: %m");
- r = bus_append_scope_pidref(m, &pidref);
- } else
- r = sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, getpid_cached());
+ r = bus_append_scope_pidref(m, &pidref, allow_pidfd);
if (r < 0)
return bus_log_create_error(r);
#include "dirent-util.h"
#include "fd-util.h"
#include "discover-image.h"
+#include "pidref.h"
#include "sd-daemon.h"
#include "sd-event.h"
#include "sd-id128.h"
#include "gpt.h"
#include "hexdecoct.h"
#include "hostname-util.h"
+#include "io-util.h"
#include "kernel-image.h"
#include "log.h"
#include "machine-credential.h"
#include "stat-util.h"
#include "string-util.h"
#include "strv.h"
+#include "time-util.h"
#include "tmpfile-util.h"
#include "unit-name.h"
#include "vmspawn-mount.h"
static char **arg_kernel_cmdline_extra = NULL;
static char **arg_extra_drives = NULL;
static char *arg_background = NULL;
+static bool arg_pass_ssh_key = true;
+static char *arg_ssh_key_type = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_directory, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
STATIC_DESTRUCTOR_REGISTER(arg_kernel_cmdline_extra, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_extra_drives, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_background, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_ssh_key_type, freep);
static int help(void) {
_cleanup_free_ char *link = NULL;
"\n%3$sIntegration:%4$s\n"
" --forward-journal=FILE|DIR\n"
" Forward the VM's journal to the host\n"
+ " --pass-ssh-key=BOOL Create an SSH key to access the VM\n"
+ " --ssh-key-type=TYPE Choose what type of SSH key to pass\n"
"\n%3$sInput/Output:%4$s\n"
" --console=MODE Console mode (interactive, native, gui)\n"
" --background=COLOR Set ANSI color for background\n"
ARG_SECURE_BOOT,
ARG_PRIVATE_USERS,
ARG_FORWARD_JOURNAL,
+ ARG_PASS_SSH_KEY,
+ ARG_SSH_KEY_TYPE,
ARG_SET_CREDENTIAL,
ARG_LOAD_CREDENTIAL,
ARG_FIRMWARE,
{ "secure-boot", required_argument, NULL, ARG_SECURE_BOOT },
{ "private-users", required_argument, NULL, ARG_PRIVATE_USERS },
{ "forward-journal", required_argument, NULL, ARG_FORWARD_JOURNAL },
+ { "pass-ssh-key", required_argument, NULL, ARG_PASS_SSH_KEY },
+ { "ssh-key-type", required_argument, NULL, ARG_SSH_KEY_TYPE },
{ "set-credential", required_argument, NULL, ARG_SET_CREDENTIAL },
{ "load-credential", required_argument, NULL, ARG_LOAD_CREDENTIAL },
{ "firmware", required_argument, NULL, ARG_FIRMWARE },
return r;
break;
+ case ARG_PASS_SSH_KEY:
+ r = parse_boolean(optarg);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse --pass-ssh-key= argument: %s", optarg);
+
+ arg_pass_ssh_key = r;
+ break;
+
+ case ARG_SSH_KEY_TYPE:
+ if (!string_is_safe(optarg))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value for --arg-ssh-key-type=: %s", optarg);
+
+ r = free_and_strdup_warn(&arg_ssh_key_type, optarg);
+ if (r < 0)
+ return r;
+ break;
+
case ARG_SET_CREDENTIAL: {
r = machine_credential_set(&arg_credentials, optarg);
if (r < 0)
if (dot)
(void) pty_forward_set_title_prefix(f, dot);
}
+static int generate_ssh_keypair(const char *key_path, const char *key_type) {
+ _cleanup_free_ char *ssh_keygen = NULL;
+ _cleanup_strv_free_ char **cmdline = NULL;
+ int r;
+
+ assert(key_path);
+
+ r = find_executable("ssh-keygen", &ssh_keygen);
+ if (r < 0)
+ return log_error_errno(r, "Failed to find ssh-keygen: %m");
+
+ cmdline = strv_new(ssh_keygen, "-f", key_path, /* don't encrypt the key */ "-N", "");
+ if (!cmdline)
+ return log_oom();
+
+ if (key_type) {
+ r = strv_extend_many(&cmdline, "-t", key_type);
+ if (r < 0)
+ return log_oom();
+ }
+
+ if (DEBUG_LOGGING) {
+ _cleanup_free_ char *joined = quote_command_line(cmdline, SHELL_ESCAPE_EMPTY);
+ if (!joined)
+ return log_oom();
+
+ log_debug("Executing: %s", joined);
+ }
+
+ r = safe_fork(
+ ssh_keygen,
+ FORK_WAIT|FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE|FORK_REARRANGE_STDIO,
+ NULL);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ execv(ssh_keygen, cmdline);
+ log_error_errno(errno, "Failed to execve %s: %m", ssh_keygen);
+ _exit(EXIT_FAILURE);
+ }
+
+ return 0;
+}
static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
_cleanup_(ovmf_config_freep) OvmfConfig *ovmf_config = NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_free_ char *machine = NULL, *qemu_binary = NULL, *mem = NULL, *trans_scope = NULL, *kernel = NULL;
+ _cleanup_(rm_rf_physical_and_freep) char *ssh_private_key_path = NULL, *ssh_public_key_path = NULL;
_cleanup_close_ int notify_sock_fd = -EBADF;
_cleanup_strv_free_ char **cmdline = NULL;
_cleanup_free_ int *pass_fds = NULL;
return r;
}
+ if (arg_pass_ssh_key) {
+ _cleanup_free_ char *scope_prefix = NULL, *privkey_path = NULL, *pubkey_path = NULL;
+ const char *key_type = arg_ssh_key_type ?: "ed25519";
+
+ r = unit_name_to_prefix(trans_scope, &scope_prefix);
+ if (r < 0)
+ return log_error_errno(r, "Failed to strip .scope suffix from scope: %m");
+
+ privkey_path = strjoin(arg_runtime_directory, "/", scope_prefix, "-", key_type);
+ if (!privkey_path)
+ return log_oom();
+
+ pubkey_path = strjoin(privkey_path, ".pub");
+ if (!pubkey_path)
+ return log_oom();
+
+ r = generate_ssh_keypair(privkey_path, key_type);
+ if (r < 0)
+ return r;
+
+ ssh_private_key_path = TAKE_PTR(privkey_path);
+ ssh_public_key_path = TAKE_PTR(pubkey_path);
+ }
+
+ if (ssh_public_key_path && ssh_private_key_path) {
+ _cleanup_free_ char *scope_prefix = NULL, *cred_path = NULL;
+
+ cred_path = strjoin("ssh.ephemeral-authorized_keys-all:", ssh_public_key_path);
+ if (!cred_path)
+ return log_oom();
+
+ r = machine_credential_load(&arg_credentials, cred_path);
+ if (r < 0)
+ return log_error_errno(r, "Failed to load credential %s: %m", cred_path);
+
+ r = unit_name_to_prefix(trans_scope, &scope_prefix);
+ if (r < 0)
+ return log_error_errno(r, "Failed to strip .scope suffix from scope: %m");
+ }
+
if (ARCHITECTURE_SUPPORTS_SMBIOS)
FOREACH_ARRAY(cred, arg_credentials.credentials, arg_credentials.n_credentials) {
_cleanup_free_ char *cred_data_b64 = NULL;
reachable in the same URL parent directory as the logs.gz that gets linked on
the Github CI status.
+The log URL can be derived following a simple algorithm, however the test
+completion timestamp is needed and it's not easy to find without access to the
+log itself. For example, a noble s390x job started on 2024-03-23 at 02:09:11
+will be stored at the following URL:
+
+https://autopkgtest.ubuntu.com/results/autopkgtest-noble-upstream-systemd-ci-systemd-ci/noble/s390x/s/systemd-upstream/20240323_020911_e8e88@/log.gz
+
+The 5 characters at the end of the last directory are not random, but the first
+5 characters of a SHA1 hash generated based on the set of parameters given to
+the build plus the completion timestamp, such as:
+
+$ echo -n 'systemd-upstream {"build-git": "https://salsa.debian.org/systemd-team/systemd.git#debian/master", "env": ["UPSTREAM_REPO=https://github.com/systemd/systemd.git", "CFLAGS=-O0", "DEB_BUILD_PROFILES=pkg.systemd.upstream noudeb", "TEST_UPSTREAM=1", "CONFFLAGS_UPSTREAM=--werror -Dslow-tests=true", "UPSTREAM_PULL_REQUEST=31444", "GITHUB_STATUSES_URL=https://api.github.com/repos/systemd/systemd/statuses/c27f600a1c47f10b22964eaedfb5e9f0d4279cd9"], "ppas": ["upstream-systemd-ci/systemd-ci"], "submit-time": "2024-02-27 17:06:27", "uuid": "02cd262f-af22-4f82-ac91-53fa5a9e7811"}' | sha1sum | cut -c1-5
+
To add new dependencies or new binaries to the packages used during the tests,
a merge request can be sent to: https://salsa.debian.org/systemd-team/systemd
targeting the 'upstream-ci' branch.
local workspace="${1:?}"
local dropin_dir
- mkdir -p "$workspace/test-journals/"
- cp -av "${TEST_BASE_DIR:?}/test-journals/"* "$workspace/test-journals/"
-
image_install curl setterm unzstd
image_install -o openssl
# Necessary for RH-based systems, otherwise MHD fails with:
foreach subdir : [
'auxv',
'journal-data',
+ 'test-journals',
'units',
'test-execute',
'test-fstab-generator',
output = networkctl_status('veth-peer')
self.assertIn(f'Offered DHCP leases: {client_address}', output)
+ # Check if the same addresses are used even if the service is restarted.
+ restart_networkd()
+ self.wait_online('veth99:routable', 'veth-peer:routable')
+
+ output = check_output('ip -4 address show dev veth-peer')
+ print(output)
+ self.assertIn(f'{server_address}', output)
+
+ output = check_output('ip -4 address show dev veth99')
+ print(output)
+ self.assertIn(f'{client_address}', output)
+
+ output = networkctl_status('veth99')
+ print(output)
+ self.assertRegex(output, rf'Address: {client_address} \(DHCP4 via {server_address}\)')
+ self.assertIn(f'Gateway: {server_address}', output)
+ self.assertIn(f'DNS: {server_address}', output)
+ self.assertIn(f'NTP: {server_address}', output)
+
+ output = networkctl_status('veth-peer')
+ self.assertIn(f'Offered DHCP leases: {client_address}', output)
+
def test_dhcp_server_with_uplink(self):
copy_network_unit('25-veth.netdev', '25-dhcp-client.network', '25-dhcp-server-downstream.network',
'12-dummy.netdev', '25-dhcp-server-uplink.network')
JOURNAL_DIR="$(mktemp -d)"
REMOTE_OUT="$(mktemp -d)"
# tar on C8S doesn't support the --zstd option
-unzstd --stdout "/test-journals/afl-corrupted-journals.tar.zst" | tar -xC "$JOURNAL_DIR/"
+unzstd --stdout "/usr/lib/systemd/tests/testdata/test-journals/afl-corrupted-journals.tar.zst" | tar -xC "$JOURNAL_DIR/"
while read -r file; do
filename="${file##*/}"
unzstd "$file" -o "$JOURNAL_DIR/${filename%*.zst}"
-done < <(find /test-journals/corrupted/ -name "*.zst")
+done < <(find /usr/lib/systemd/tests/testdata/test-journals/corrupted/ -name "*.zst")
# First, try each of them sequentially. Skip this part when running with plain
# QEMU, as it is excruciatingly slow
# Note: we care only about exit code 124 (timeout) and special bash exit codes
while read -r file; do
filename="${file##*/}"
unzstd "$file" -o "$JOURNAL_DIR/${filename%*.zst}"
-done < <(find /test-journals/no-rtc -name "*.zst")
+done < <(find /usr/lib/systemd/tests/testdata/test-journals/no-rtc -name "*.zst")
journalctl --directory="$JOURNAL_DIR" --list-boots --output=json >/tmp/lb1
diff -u /tmp/lb1 - <<'EOF'
bash -xec 'timeout 1s nc -6 -u -l ::1 9999; exit 42'
systemd-run --wait -p SuccessExitStatus="1 2" --pipe "${ARGUMENTS[@]}" \
bash -xec 'timeout 1s nc -4 -l 127.0.0.1 6666; exit 42'
+ systemd-run --wait -p SuccessExitStatus="1 2" --pipe -p SocketBindDeny=any \
+ bash -xec 'timeout 1s nc -l 127.0.0.1 9999; exit 42'
# Consequently, we should succeed when binding to a socket on the allow list
# and keep listening on it until we're killed by `timeout` (EC 124)
systemd-run --wait --pipe -p SuccessExitStatus=124 "${ARGUMENTS[@]}" \
# we enable --luks-discard= since we run our tests in a tight VM, hence don't
# needlessly pressure for storage. We also set the cheapest KDF, since we don't
-# want to waste CI CPU cycles on it.
+# want to waste CI CPU cycles on it. We also effectively disable rate-limiting on
+# the user by allowing 1000 logins per second
NEWPASSWORD=xEhErW0ndafV4s homectl create test-user \
--disk-size=min \
--luks-discard=yes \
--image-path=/home/test-user.home \
--luks-pbkdf-type=pbkdf2 \
- --luks-pbkdf-time-cost=1ms
+ --luks-pbkdf-time-cost=1ms \
+ --rate-limit-interval=1s \
+ --rate-limit-burst=1000
inspect test-user
PASSWORD=xEhErW0ndafV4s homectl authenticate test-user
homectl deactivate test-user
inspect test-user
+homectl update test-user --real-name "Offline test" --offline
+inspect test-user
+
PASSWORD=xEhErW0ndafV4s homectl activate test-user
inspect test-user
+# Ensure that the offline changes were propagated in
+grep "Offline test" /home/test-user/.identity
+
homectl deactivate test-user
inspect test-user
-PASSWORD=xEhErW0ndafV4s homectl update test-user --real-name="Offline test"
+PASSWORD=xEhErW0ndafV4s homectl update test-user --real-name="Inactive test"
inspect test-user
PASSWORD=xEhErW0ndafV4s homectl activate test-user
homectl deactivate test-user
inspect test-user
+# Do some keyring tests, but only on real kernels, since keyring access inside of containers will fail
+# (See: https://github.com/systemd/systemd/issues/17606)
+if ! systemd-detect-virt -cq ; then
+ PASSWORD=xEhErW0ndafV4s homectl activate test-user
+ inspect test-user
+
+ # Key should now be in the keyring
+ homectl update test-user --real-name "Keyring Test"
+ inspect test-user
+
+ # These commands shouldn't use the keyring
+ (! timeout 5s homectl authenticate test-user )
+ (! NEWPASSWORD="foobar" timeout 5s homectl passwd test-user )
+
+ homectl lock test-user
+ inspect test-user
+
+ # Key should be gone from keyring
+ (! timeout 5s homectl update test-user --real-name "Keyring Test 2" )
+
+ PASSWORD=xEhErW0ndafV4s homectl unlock test-user
+ inspect test-user
+
+ # Key should have been re-instantiated into the keyring
+ homectl update test-user --real-name "Keyring Test 3"
+ inspect test-user
+
+ homectl deactivate test-user
+ inspect test-user
+fi
+
# Do some resize tests, but only if we run on real kernels, as quota inside of containers will fail
if ! systemd-detect-virt -cq ; then
# grow while inactive
--luks-discard=yes \
--image-path=/home/test-user2.home \
--luks-pbkdf-type=pbkdf2 \
- --luks-pbkdf-time-cost=1ms
+ --luks-pbkdf-time-cost=1ms \
+ --rate-limit-interval=1s \
+ --rate-limit-burst=1000
inspect test-user2
# activate second user
homectl rebalance
inspect test-user
inspect test-user2
+
+ wait_for_state test-user2 active
+ homectl deactivate test-user2
+ wait_for_state test-user2 inactive
+ homectl remove test-user2
fi
PASSWORD=xEhErW0ndafV4s homectl with test-user -- test ! -f /home/test-user/xyz
(! PASSWORD=xEhErW0ndafV4s homectl with test-user -- test -f /home/test-user/xyz)
PASSWORD=xEhErW0ndafV4s homectl with test-user -- touch /home/test-user/xyz
PASSWORD=xEhErW0ndafV4s homectl with test-user -- test -f /home/test-user/xyz
-# CAREFUL adding more `homectl with` tests here. Auth can get rate-limited and cause the tests to fail.
+PASSWORD=xEhErW0ndafV4s homectl with test-user -- rm /home/test-user/xyz
+PASSWORD=xEhErW0ndafV4s homectl with test-user -- test ! -f /home/test-user/xyz
+(! PASSWORD=xEhErW0ndafV4s homectl with test-user -- test -f /home/test-user/xyz)
wait_for_state test-user inactive
homectl remove test-user
-if ! systemd-detect-virt -cq ; then
- wait_for_state test-user2 active
- homectl deactivate test-user2
- wait_for_state test-user2 inactive
- homectl remove test-user2
-fi
-
# blob directory tests
# See docs/USER_RECORD_BLOB_DIRS.md
checkblob() {
NEWPASSWORD=EMJuc3zQaMibJo homectl create blob-user \
--disk-size=min --luks-discard=yes \
--luks-pbkdf-type=pbkdf2 --luks-pbkdf-time-cost=1ms \
+ --rate-limit-interval=1s --rate-limit-burst=1000 \
--uid=12345 \
--blob=/tmp/blob1
inspect blob-user
(! PASSWORD=EMJuc3zQaMibJo homectl update blob-user -b файл=/tmp/external-test3 )
(! PASSWORD=EMJuc3zQaMibJo homectl update blob-user -b special@chars=/tmp/external-test3 )
+# Make sure offline updates to blobs get propagated in
+homectl deactivate blob-user
+inspect blob-user
+homectl update blob-user --offline -b barely-fits= -b propagated=/tmp/external-test3
+inspect blob-user
+PASSWORD=EMJuc3zQaMibJo homectl activate blob-user
+inspect blob-user
+(! checkblob barely-fits /tmp/external-barely-fits )
+checkblob propagated /tmp/external-test3
+
homectl deactivate blob-user
wait_for_state blob-user inactive
homectl remove blob-user
--luks-discard=yes \
--luks-pbkdf-type=pbkdf2 \
--luks-pbkdf-time-cost=1ms \
+ --rate-limit-interval=1s \
+ --rate-limit-burst=1000 \
--enforce-password-policy=no \
--ssh-authorized-keys=@/tmp/homed.id_ecdsa.pub \
--stop-delay=0 \
--- /dev/null
+#!/usr/bin/python3
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# vi: set tw=110 sw=4 ts=4 et:
+
+import sys
+
+import pefile
+
+
+def main():
+ pe = pefile.PE(sys.argv[1], fast_load=True)
+
+ for section in pe.sections:
+ name = section.Name.rstrip(b"\x00").decode()
+ file_addr = section.PointerToRawData
+ virt_addr = section.VirtualAddress
+ print(f"{name:10s} file=0x{file_addr:08x} virt=0x{virt_addr:08x}")
+
+ if file_addr % 512 != 0:
+ print(f"File address of {name} section is not aligned to 512 bytes", file=sys.stderr)
+ return 1
+
+ if virt_addr % 512 != 0:
+ print(f"Virt address of {name} section is not aligned to 512 bytes", file=sys.stderr)
+ return 1
+
+if __name__ == '__main__':
+ if len(sys.argv) != 2:
+ print(f"Usage: {sys.argv[0]} pe-image")
+ sys.exit(1)
+
+ sys.exit(main())
import io
import os
import pathlib
+import sys
import time
import typing
from ctypes import (
class PeCoffHeader(LittleEndianStructure):
_fields_ = (
- ("Machine", c_uint16),
- ("NumberOfSections", c_uint16),
- ("TimeDateStamp", c_uint32),
+ ("Machine", c_uint16),
+ ("NumberOfSections", c_uint16),
+ ("TimeDateStamp", c_uint32),
("PointerToSymbolTable", c_uint32),
- ("NumberOfSymbols", c_uint32),
+ ("NumberOfSymbols", c_uint32),
("SizeOfOptionalHeader", c_uint16),
- ("Characteristics", c_uint16),
+ ("Characteristics", c_uint16),
)
class PeDataDirectory(LittleEndianStructure):
_fields_ = (
("VirtualAddress", c_uint32),
- ("Size", c_uint32),
+ ("Size", c_uint32),
)
class PeRelocationBlock(LittleEndianStructure):
_fields_ = (
- ("PageRVA", c_uint32),
+ ("PageRVA", c_uint32),
("BlockSize", c_uint32),
)
class PeRelocationEntry(LittleEndianStructure):
_fields_ = (
("Offset", c_uint16, 12),
- ("Type", c_uint16, 4),
+ ("Type", c_uint16, 4),
)
class PeOptionalHeaderStart(LittleEndianStructure):
_fields_ = (
- ("Magic", c_uint16),
- ("MajorLinkerVersion", c_uint8),
- ("MinorLinkerVersion", c_uint8),
- ("SizeOfCode", c_uint32),
- ("SizeOfInitializedData", c_uint32),
+ ("Magic", c_uint16),
+ ("MajorLinkerVersion", c_uint8),
+ ("MinorLinkerVersion", c_uint8),
+ ("SizeOfCode", c_uint32),
+ ("SizeOfInitializedData", c_uint32),
("SizeOfUninitializedData", c_uint32),
- ("AddressOfEntryPoint", c_uint32),
- ("BaseOfCode", c_uint32),
+ ("AddressOfEntryPoint", c_uint32),
+ ("BaseOfCode", c_uint32),
)
class PeOptionalHeaderMiddle(LittleEndianStructure):
_fields_ = (
- ("SectionAlignment", c_uint32),
- ("FileAlignment", c_uint32),
+ ("SectionAlignment", c_uint32),
+ ("FileAlignment", c_uint32),
("MajorOperatingSystemVersion", c_uint16),
("MinorOperatingSystemVersion", c_uint16),
- ("MajorImageVersion", c_uint16),
- ("MinorImageVersion", c_uint16),
- ("MajorSubsystemVersion", c_uint16),
- ("MinorSubsystemVersion", c_uint16),
- ("Win32VersionValue", c_uint32),
- ("SizeOfImage", c_uint32),
- ("SizeOfHeaders", c_uint32),
- ("CheckSum", c_uint32),
- ("Subsystem", c_uint16),
- ("DllCharacteristics", c_uint16),
+ ("MajorImageVersion", c_uint16),
+ ("MinorImageVersion", c_uint16),
+ ("MajorSubsystemVersion", c_uint16),
+ ("MinorSubsystemVersion", c_uint16),
+ ("Win32VersionValue", c_uint32),
+ ("SizeOfImage", c_uint32),
+ ("SizeOfHeaders", c_uint32),
+ ("CheckSum", c_uint32),
+ ("Subsystem", c_uint16),
+ ("DllCharacteristics", c_uint16),
)
class PeOptionalHeaderEnd(LittleEndianStructure):
_fields_ = (
- ("LoaderFlags", c_uint32),
- ("NumberOfRvaAndSizes", c_uint32),
- ("ExportTable", PeDataDirectory),
- ("ImportTable", PeDataDirectory),
- ("ResourceTable", PeDataDirectory),
- ("ExceptionTable", PeDataDirectory),
- ("CertificateTable", PeDataDirectory),
- ("BaseRelocationTable", PeDataDirectory),
- ("Debug", PeDataDirectory),
- ("Architecture", PeDataDirectory),
- ("GlobalPtr", PeDataDirectory),
- ("TLSTable", PeDataDirectory),
- ("LoadConfigTable", PeDataDirectory),
- ("BoundImport", PeDataDirectory),
- ("IAT", PeDataDirectory),
+ ("LoaderFlags", c_uint32),
+ ("NumberOfRvaAndSizes", c_uint32),
+ ("ExportTable", PeDataDirectory),
+ ("ImportTable", PeDataDirectory),
+ ("ResourceTable", PeDataDirectory),
+ ("ExceptionTable", PeDataDirectory),
+ ("CertificateTable", PeDataDirectory),
+ ("BaseRelocationTable", PeDataDirectory),
+ ("Debug", PeDataDirectory),
+ ("Architecture", PeDataDirectory),
+ ("GlobalPtr", PeDataDirectory),
+ ("TLSTable", PeDataDirectory),
+ ("LoadConfigTable", PeDataDirectory),
+ ("BoundImport", PeDataDirectory),
+ ("IAT", PeDataDirectory),
("DelayImportDescriptor", PeDataDirectory),
- ("CLRRuntimeHeader", PeDataDirectory),
- ("Reserved", PeDataDirectory),
+ ("CLRRuntimeHeader", PeDataDirectory),
+ ("Reserved", PeDataDirectory),
)
class PeOptionalHeader32(PeOptionalHeader):
_anonymous_ = ("Start", "Middle", "End")
_fields_ = (
- ("Start", PeOptionalHeaderStart),
- ("BaseOfData", c_uint32),
- ("ImageBase", c_uint32),
- ("Middle", PeOptionalHeaderMiddle),
+ ("Start", PeOptionalHeaderStart),
+ ("BaseOfData", c_uint32),
+ ("ImageBase", c_uint32),
+ ("Middle", PeOptionalHeaderMiddle),
("SizeOfStackReserve", c_uint32),
- ("SizeOfStackCommit", c_uint32),
- ("SizeOfHeapReserve", c_uint32),
- ("SizeOfHeapCommit", c_uint32),
- ("End", PeOptionalHeaderEnd),
+ ("SizeOfStackCommit", c_uint32),
+ ("SizeOfHeapReserve", c_uint32),
+ ("SizeOfHeapCommit", c_uint32),
+ ("End", PeOptionalHeaderEnd),
)
class PeOptionalHeader32Plus(PeOptionalHeader):
_anonymous_ = ("Start", "Middle", "End")
_fields_ = (
- ("Start", PeOptionalHeaderStart),
- ("ImageBase", c_uint64),
- ("Middle", PeOptionalHeaderMiddle),
+ ("Start", PeOptionalHeaderStart),
+ ("ImageBase", c_uint64),
+ ("Middle", PeOptionalHeaderMiddle),
("SizeOfStackReserve", c_uint64),
- ("SizeOfStackCommit", c_uint64),
- ("SizeOfHeapReserve", c_uint64),
- ("SizeOfHeapCommit", c_uint64),
- ("End", PeOptionalHeaderEnd),
+ ("SizeOfStackCommit", c_uint64),
+ ("SizeOfHeapReserve", c_uint64),
+ ("SizeOfHeapCommit", c_uint64),
+ ("End", PeOptionalHeaderEnd),
)
class PeSection(LittleEndianStructure):
_fields_ = (
- ("Name", c_char * 8),
- ("VirtualSize", c_uint32),
- ("VirtualAddress", c_uint32),
- ("SizeOfRawData", c_uint32),
- ("PointerToRawData", c_uint32),
+ ("Name", c_char * 8),
+ ("VirtualSize", c_uint32),
+ ("VirtualAddress", c_uint32),
+ ("SizeOfRawData", c_uint32),
+ ("PointerToRawData", c_uint32),
("PointerToRelocations", c_uint32),
("PointerToLinenumbers", c_uint32),
- ("NumberOfRelocations", c_uint16),
- ("NumberOfLinenumbers", c_uint16),
- ("Characteristics", c_uint32),
+ ("NumberOfRelocations", c_uint16),
+ ("NumberOfLinenumbers", c_uint16),
+ ("Characteristics", c_uint32),
)
def __init__(self):
PE_CHARACTERISTICS_RX = 0x60000020 # CNT_CODE|MEM_READ|MEM_EXECUTE
PE_CHARACTERISTICS_RW = 0xC0000040 # CNT_INITIALIZED_DATA|MEM_READ|MEM_WRITE
-PE_CHARACTERISTICS_R = 0x40000040 # CNT_INITIALIZED_DATA|MEM_READ
+PE_CHARACTERISTICS_R = 0x40000040 # CNT_INITIALIZED_DATA|MEM_READ
IGNORE_SECTIONS = [
".eh_frame",
".eh_frame_hdr",
".ARM.exidx",
+ ".relro_padding",
]
IGNORE_SECTION_TYPES = [
def next_section_address(sections: typing.List[PeSection]) -> int:
- return align_to(
- sections[-1].VirtualAddress + sections[-1].VirtualSize, SECTION_ALIGNMENT
- )
+ return align_to(sections[-1].VirtualAddress + sections[-1].VirtualSize,
+ SECTION_ALIGNMENT)
+
+
+class BadSectionError(ValueError):
+ "One of the sections is in a bad state"
def iter_copy_sections(elf: ELFFile) -> typing.Iterator[PeSection]:
relro = None
for elf_seg in elf.iter_segments():
if elf_seg["p_type"] == "PT_LOAD" and elf_seg["p_align"] != SECTION_ALIGNMENT:
- raise RuntimeError("ELF segments are not properly aligned.")
- elif elf_seg["p_type"] == "PT_GNU_RELRO":
+ raise BadSectionError(f"ELF segment {elf_seg['p_type']} is not properly aligned"
+ f" ({elf_seg['p_align']} != {SECTION_ALIGNMENT})")
+ if elf_seg["p_type"] == "PT_GNU_RELRO":
relro = elf_seg
for elf_s in elf.iter_sections():
elf_s["sh_flags"] & SH_FLAGS.SHF_ALLOC == 0
or elf_s["sh_type"] in IGNORE_SECTION_TYPES
or elf_s.name in IGNORE_SECTIONS
+ or elf_s["sh_size"] == 0
):
continue
if elf_s["sh_type"] not in ["SHT_PROGBITS", "SHT_NOBITS"]:
- raise RuntimeError(f"Unknown section {elf_s.name}.")
+ raise BadSectionError(f"Unknown section {elf_s.name} with type {elf_s['sh_type']}")
+ if elf_s.name == '.got':
+ # FIXME: figure out why those sections are inserted
+ print("WARNING: Non-empty .got section", file=sys.stderr)
if elf_s["sh_flags"] & SH_FLAGS.SHF_EXECINSTR:
rwx = PE_CHARACTERISTICS_RX
def convert_sections(elf: ELFFile, opt: PeOptionalHeader) -> typing.List[PeSection]:
- last_vma = 0
+ last_vma = (0, 0)
sections = []
for pe_s in iter_copy_sections(elf):
PE_CHARACTERISTICS_R: b".rodata",
}[pe_s.Characteristics]
- # This can happen if not building with `-z separate-code`.
- if pe_s.VirtualAddress < last_vma:
- raise RuntimeError("Overlapping PE sections.")
- last_vma = pe_s.VirtualAddress + pe_s.VirtualSize
+ # This can happen if not building with '-z separate-code'.
+ if pe_s.VirtualAddress < sum(last_vma):
+ raise BadSectionError(f"Section {pe_s.Name.decode()!r} @0x{pe_s.VirtualAddress:x} overlaps"
+ f" previous section @0x{last_vma[0]:x}+0x{last_vma[1]:x}=@0x{sum(last_vma):x}")
+ last_vma = (pe_s.VirtualAddress, pe_s.VirtualSize)
if pe_s.Name == b".text":
opt.BaseOfCode = pe_s.VirtualAddress
if not elf_s:
continue
if elf_s.data_alignment > 1 and SECTION_ALIGNMENT % elf_s.data_alignment != 0:
- raise RuntimeError(f"ELF section {name} is not aligned.")
+ raise BadSectionError(f"ELF section {name} is not aligned")
if elf_s["sh_flags"] & (SH_FLAGS.SHF_EXECINSTR | SH_FLAGS.SHF_WRITE) != 0:
- raise RuntimeError(f"ELF section {name} is not read-only data.")
+ raise BadSectionError(f"ELF section {name} is not read-only data")
pe_s = PeSection()
pe_s.Name = name.encode()
sections: typing.List[PeSection],
addend_size: int,
):
- # fmt: off
- [target] = [
- pe_s for pe_s in sections
- if pe_s.VirtualAddress <= reloc["r_offset"] < pe_s.VirtualAddress + len(pe_s.data)
- ]
- # fmt: on
+ [target] = [pe_s for pe_s in sections
+ if pe_s.VirtualAddress <= reloc["r_offset"] < pe_s.VirtualAddress + len(pe_s.data)]
addend_offset = reloc["r_offset"] - target.VirtualAddress
continue
if reloc["r_info_type"] == RELATIVE_RELOC:
- apply_elf_relative_relocation(
- reloc, elf_image_base, sections, elf.elfclass // 8
- )
+ apply_elf_relative_relocation(reloc,
+ elf_image_base,
+ sections,
+ elf.elfclass // 8)
# Now that the ELF relocation has been applied, we can create a PE relocation.
block_rva = reloc["r_offset"] & ~0xFFF
continue
- raise RuntimeError(f"Unsupported relocation {reloc}")
+ raise BadSectionError(f"Unsupported relocation {reloc}")
def convert_elf_relocations(
) -> typing.Optional[PeSection]:
dynamic = elf.get_section_by_name(".dynamic")
if dynamic is None:
- raise RuntimeError("ELF .dynamic section is missing.")
+ raise BadSectionError("ELF .dynamic section is missing")
[flags_tag] = dynamic.iter_tags("DT_FLAGS_1")
if not flags_tag["d_val"] & ENUM_DT_FLAGS_1["DF_1_PIE"]:
- raise RuntimeError("ELF file is not a PIE.")
+ raise ValueError("ELF file is not a PIE")
# This checks that the ELF image base is 0.
symtab = elf.get_section_by_name(".symtab")
if symtab:
exe_start = symtab.get_symbol_by_name("__executable_start")
if exe_start and exe_start[0]["st_value"] != 0:
- raise RuntimeError("Unexpected ELF image base.")
-
- opt.SizeOfHeaders = align_to(
- PE_OFFSET
- + len(PE_MAGIC)
- + sizeof(PeCoffHeader)
- + sizeof(opt)
- + sizeof(PeSection) * max(len(sections) + 1, minimum_sections),
- FILE_ALIGNMENT,
- )
+ raise ValueError("Unexpected ELF image base")
+
+ opt.SizeOfHeaders = align_to(PE_OFFSET
+ + len(PE_MAGIC)
+ + sizeof(PeCoffHeader)
+ + sizeof(opt)
+ + sizeof(PeSection) * max(len(sections) + 1, minimum_sections),
+ FILE_ALIGNMENT)
# We use the basic VMA layout from the ELF image in the PE image. This could cause the first
# section to overlap the PE image headers during runtime at VMA 0. We can simply apply a fixed
# the ELF portions of the image.
segment_offset = 0
if sections[0].VirtualAddress < opt.SizeOfHeaders:
- segment_offset = align_to(
- opt.SizeOfHeaders - sections[0].VirtualAddress, SECTION_ALIGNMENT
- )
+ segment_offset = align_to(opt.SizeOfHeaders - sections[0].VirtualAddress,
+ SECTION_ALIGNMENT)
opt.AddressOfEntryPoint = elf["e_entry"] + segment_offset
opt.BaseOfCode += segment_offset
pe_reloc_blocks: typing.Dict[int, PeRelocationBlock] = {}
for reloc_type, reloc_table in dynamic.get_relocation_tables().items():
if reloc_type not in ["REL", "RELA"]:
- raise RuntimeError("Unsupported relocation type {elf_reloc_type}.")
- convert_elf_reloc_table(
- elf, reloc_table, opt.ImageBase + segment_offset, sections, pe_reloc_blocks
- )
+ raise BadSectionError(f"Unsupported relocation type {reloc_type}")
+ convert_elf_reloc_table(elf,
+ reloc_table,
+ opt.ImageBase + segment_offset,
+ sections,
+ pe_reloc_blocks)
for pe_s in sections:
pe_s.VirtualAddress += segment_offset
block.entries.append(PeRelocationEntry())
block.PageRVA += segment_offset
- block.BlockSize = (
- sizeof(PeRelocationBlock) + sizeof(PeRelocationEntry) * n_relocs
- )
+ block.BlockSize = sizeof(PeRelocationBlock) + sizeof(PeRelocationEntry) * n_relocs
data += block
for entry in sorted(block.entries, key=lambda e: e.Offset):
data += entry
def write_pe(
- file, coff: PeCoffHeader, opt: PeOptionalHeader, sections: typing.List[PeSection]
+ file,
+ coff: PeCoffHeader,
+ opt: PeOptionalHeader,
+ sections: typing.List[PeSection],
):
file.write(b"MZ")
file.seek(0x3C, io.SEEK_SET)
offset = opt.SizeOfHeaders
for pe_s in sorted(sections, key=lambda s: s.VirtualAddress):
if pe_s.VirtualAddress < opt.SizeOfHeaders:
- raise RuntimeError(f"Section {pe_s.Name} overlapping PE headers.")
+ raise BadSectionError(f"Section {pe_s.Name} @0x{pe_s.VirtualAddress:x} overlaps"
+ " PE headers ending at 0x{opt.SizeOfHeaders:x}")
pe_s.PointerToRawData = offset
file.write(pe_s)
def elf2efi(args: argparse.Namespace):
elf = ELFFile(args.ELF)
if not elf.little_endian:
- raise RuntimeError("ELF file is not little-endian.")
+ raise ValueError("ELF file is not little-endian")
if elf["e_type"] not in ["ET_DYN", "ET_EXEC"]:
- raise RuntimeError("Unsupported ELF type.")
+ raise ValueError(f"Unsupported ELF type {elf['e_type']}")
pe_arch = {
"EM_386": 0x014C,
"EM_X86_64": 0x8664,
}.get(elf["e_machine"])
if pe_arch is None:
- raise RuntimeError(f"Unsupported ELF arch {elf['e_machine']}")
+ raise ValueError(f"Unsupported ELF architecture {elf['e_machine']}")
coff = PeCoffHeader()
opt = PeOptionalHeader32() if elf.elfclass == 32 else PeOptionalHeader32Plus()
write_pe(args.PE, coff, opt, sections)
-def main():
+def create_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="Convert ELF binaries to PE/EFI")
parser.add_argument(
"--version-major",
default="",
help="Copy these sections if found",
)
+ return parser
+
+def main():
+ parser = create_parser()
elf2efi(parser.parse_args())