Compare commits

..

28 Commits

Author SHA1 Message Date
Zbigniew Jędrzejewski-Szmek 47b04ef632
Merge pull request #16925 from cgzones/selinux_create_label
selinux/core: create several file objects with default SELinux context
2020-09-01 22:19:52 +02:00
Lennart Poettering 004cfad218
Merge pull request #16911 from keszybz/userdb-json-invalid-utf8
Deal properly with invalid utf-8 in userdb entries
2020-09-01 20:34:22 +02:00
Zbigniew Jędrzejewski-Szmek 2c0e46735b
Merge pull request #16922 from keszybz/ordered-set-ensure-allocated
Ordered set ensure allocated
2020-09-01 19:44:28 +02:00
Lennart Poettering 895abf3fdd
Merge pull request #16727 from wusto/core-fix-securebits
core: fix securebits setting
2020-09-01 17:21:48 +02:00
Renaud Métrich 3e5f04bf64 socket: New option 'FlushPending' (boolean) to flush socket before entering listening state
Disabled by default. When Enabled, before listening on the socket, flush the content.
Applies when Accept=no only.
2020-09-01 17:20:23 +02:00
Zbigniew Jędrzejewski-Szmek ceaf24d4d3 TODO: add entry 2020-09-01 16:48:40 +02:00
Zbigniew Jędrzejewski-Szmek 5c0b738012 user-record-nss: check if strings from pwd/spwd/grp/sgrp are valid utf-8
strv_extend_strv_utf8_only() uses a temporary buffer to make the implementation
conscise. Otherwise we'd have to rewrite all of strv_extend_strv() which didn't
seem worth the trouble for this one use outside of a hot path.

If the data is not serializable, we just pretend it doesn't exists.
This fixes #16683 and https://bugs.gentoo.org/735072 in a second way.
2020-09-01 16:48:40 +02:00
Zbigniew Jędrzejewski-Szmek e60775cb7b shared: merge {user,group}-record-nss.{c,h}
They both are both short and contain similar parts and various helper will be
shared between both parts of the code so it's easier to use a single file.
2020-09-01 16:48:40 +02:00
Zbigniew Jędrzejewski-Szmek 4d7f51756a test-json: add function headers 2020-09-01 16:48:40 +02:00
Zbigniew Jędrzejewski-Szmek ea9afe0064 shared/json: reject non-utf-8 strings
JSON strings must be utf-8-clean. We also verify this in json_parse_string()
so we would reject a message with invalid utf-8 anyway.

It would probably be slightly cheaper to detect non-conformaning strings in
serialization, but then we'd have to fail serialization. By doing this early,
we give the caller a chance to handle the error nicely.

The test is adjusted to contain a valid utf-8 string after decoding of the
utf-32 encoding in json ("विवेकख्यातिरविप्लवा हानोपायः।", something about the
cessation of ignorance).
2020-09-01 16:48:40 +02:00
Zbigniew Jędrzejewski-Szmek 80ab31a435 shared/utf8: add utf8_is_valid_n()
Sometimes we need to check strings without the terminating NUL. Add a variant
that does that.
2020-09-01 16:48:40 +02:00
Christian Göttsche 63e00ccd8e selinux: create /run/systemd/userdb directory and sockets with default SELinux context 2020-09-01 16:26:12 +02:00
Christian Göttsche 45ae2f725e selinux: create systemd/notify socket with default SELinux context 2020-09-01 16:25:06 +02:00
Christian Göttsche a3f5fd964b selinux: create unit invocation links with default SELinux context 2020-09-01 15:48:53 +02:00
Zbigniew Jędrzejewski-Szmek 8a35af80fc basic/hashmap,set: move pointer symbol adjactent to the returned value
I think this is nicer in general, and here in particular we have a lot
of code like:
 static inline IteratedCache* hashmap_iterated_cache_new(Hashmap *h) {
         return (IteratedCache*) _hashmap_iterated_cache_new(HASHMAP_BASE(h));
 }
and it's visually appealing to use the same whitespace in the function
signature and the cast in the body of the function.
2020-09-01 13:45:51 +02:00
Zbigniew Jędrzejewski-Szmek e4126adf45 basic/hashmap,set: inline trivial set_iterate() wrapper
The compiler would do this to, esp. with LTO, but we can short-circuit the
whole process and make everything a bit simpler by avoiding the separate
definition.

(It would be nice to do the same for _set_new(), _set_ensure_allocated()
and other similar functions which are one-line trivial wrappers too. Unfortunately
that would require enum HashmapType to be made public, which we don't want
to do.)
2020-09-01 13:32:02 +02:00
Zbigniew Jędrzejewski-Szmek 1f25c71d9d basic: pass allocation info for ordered_set_new() and introduce ordered_set_ensure_put() 2020-09-01 12:42:35 +02:00
Susant Sahani b7847e05f5 basic: Introduce ordered_hashmap_ensure_put 2020-09-01 12:32:48 +02:00
Tobias Kaufmann dbdc4098f6 core: fix securebits setting
Desired functionality:
Set securebits for services started as non-root user.

Failure:
The starting of the service fails if no ambient capability shall be
raised.
... systemd[217941]: ...: Failed to set process secure bits: Operation
not permitted
... systemd[217941]: ...: Failed at step SECUREBITS spawning
/usr/bin/abc.service: Operation not permitted
... systemd[1]: abc.service: Failed with result 'exit-code'.

Reason:
For setting securebits the capability CAP_SETPCAP is required. However
the securebits (if no ambient capability shall be raised) are set after
setresuid.
When setresuid is invoked all capabilities are dropped from the
permitted, effective and ambient capability set. If the securebit
SECBIT_KEEP_CAPS is set the permitted capability set is retained, but
the effective and the ambient set are cleared.

If ambient capabilities shall be set, the securebit SECBIT_KEEP_CAPS is
added to the securebits configured in the service file and set together
with the securebits from the service file before setresuid is executed
(in enforce_user).
Before setresuid is executed the capabilities are the same as for pid1.
This means that all capabilities in the effective, permitted and
bounding set are set. Thus the capability CAP_SETPCAP is in the
effective set and the prctl(PR_SET_SECUREBITS, ...) succeeds.
However, if the secure bits aren't set before setresuid is invoked they
shall be set shortly after the uid change in enforce_user.
This fails as SECBIT_KEEP_CAPS wasn't set before setresuid and in
consequence the effective and permitted set was cleared, hence
CAP_SETPCAP is not set in the effective set (and cannot be raised any
longer) and prctl(PR_SET_SECUREBITS, ...) failes with EPERM.

Proposed solution:
The proposed solution consists of three parts
1. Check in enforce_user, if securebits are configured in the service
   file. If securebits are configured, set SECBIT_KEEP_CAPS
   before invoking setresuid.
2. Don't set any other securebits than SECBIT_KEEP_CAPS in enforce_user,
   but set all requested ones after enforce_user.
   This has the advantage that securebits are set at the same place for
   root and non-root services.
3. Raise CAP_SETPCAP to the effective set (if not already set) before
   setting the securebits to avoid EPERM during the prctl syscall.

For gaining CAP_SETPCAP the function capability_bounding_set_drop is
splitted into two functions:
- The first one raises CAP_SETPCAP (required for dropping bounding
  capabilities)
- The second drops the bounding capabilities

Why are ambient capabilities not affected by this change?
Ambient capabilities get cleared during setresuid, no matter if
SECBIT_KEEP_CAPS is set or not.
For raising ambient capabilities for a user different to root, the
requested capability has to be raised in the inheritable set first. Then
the SECBIT_KEEP_CAPS securebit needs to be set before setresuid is
invoked. Afterwards the ambient capability can be raised, because it is
in the inheritable and permitted set.

Security considerations:
Although the manpage is ambiguous SECBIT_KEEP_CAPS is cleared during
execve no matter if SECBIT_KEEP_CAPS_LOCKED is set or not. If both are
set only SECBIT_KEEP_CAPS_LOCKED is set after execve.
Setting SECBIT_KEEP_CAPS in enforce_user for being able to set
securebits is no security risk, as the effective and permitted set are
set to the value of the ambient set during execve (if the executed file
has no file capabilities. For details check man 7 capabilities).

Remark:
In capability-util.c is a comment complaining about the missing
capability CAP_SETPCAP in the effective set, after the kernel executed
/sbin/init. Thus it is checked there if this capability has to be raised
in the effective set before dropping capabilities from the bounding set.
If this were true all the time, ambient capabilities couldn't be set
without dropping at least one capability from the bounding set, as the
capability CAP_SETPCAP would miss and setting SECBIT_KEEP_CAPS would
fail with EPERM.
2020-09-01 10:53:26 +02:00
Tobias Kaufmann 57d4d284c9 capability-util: add new function for raising setpcap
Up to now the capability CAP_SETPCAP was raised implicitly in the
function capability_bounding_set_drop.

This functionality is moved into a new function
(capability_gain_cap_setpcap).

The new function optionally provides the capability set as it was
before raisining CAP_SETPCAP.
2020-09-01 10:53:26 +02:00
Zbigniew Jędrzejewski-Szmek e12b6e1951 json: split out string formatting to a separate function
It's complicated enough to deserve it's own function.

No functional change.
2020-09-01 10:48:10 +02:00
Zbigniew Jędrzejewski-Szmek 77472d06a4 varlink: do not parse invalid messages twice
Upon reception of a message which fails in json_parse(), we would proceed to
parse it again from a deferred callback and hang. Once we have realized that
the message is invalid, let's move the pointer in the buffer even if the
message is invalid. We don't want to look at this data again.

(before) $ build-rawhide/userdbctl --output=json user test.user
n/a: varlink: setting state idle-client
/run/systemd/userdb/io.systemd.Multiplexer: Sending message: {"method":"io.systemd.UserDatabase.GetUserRecord","parameters":{"userName":"test.user","service":"io.systemd.Multiplexer"}}
/run/systemd/userdb/io.systemd.Multiplexer: varlink: changing state idle-client → awaiting-reply
/run/systemd/userdb/io.systemd.Multiplexer: New incoming message: {...}
/run/systemd/userdb/io.systemd.Multiplexer: varlink: changing state awaiting-reply → pending-disconnect
/run/systemd/userdb/io.systemd.Multiplexer: New incoming message: {...}
/run/systemd/userdb/io.systemd.Multiplexer: varlink: changing state pending-disconnect → disconnected
^C

(after) $ n/a: varlink: setting state idle-client
/run/systemd/userdb/io.systemd.Multiplexer: Sending message: {"method":"io.systemd.UserDatabase.GetUserRecord","parameters":{"userName":"test.user","service":"io.systemd.Multiplexer"}}
/run/systemd/userdb/io.systemd.Multiplexer: varlink: changing state idle-client → awaiting-reply
/run/systemd/userdb/io.systemd.Multiplexer: New incoming message: {...}
/run/systemd/userdb/io.systemd.Multiplexer: Failed to parse JSON: Invalid argument
/run/systemd/userdb/io.systemd.Multiplexer: varlink: changing state awaiting-reply → pending-disconnect
/run/systemd/userdb/io.systemd.Multiplexer: varlink: changing state pending-disconnect → processing-disconnect
Got lookup error: io.systemd.Disconnected
/run/systemd/userdb/io.systemd.Multiplexer: varlink: changing state processing-disconnect → disconnected
Failed to find user test.user: Input/output error

This should fix #16683 and https://bugs.gentoo.org/735072.
2020-09-01 10:48:10 +02:00
Zbigniew Jędrzejewski-Szmek f7dc8248d3 man: add hint how to show password strings with userdbctl
I started working on a command-line switch to show passwords also in
"pretty" mode. I can submit that code for review if anyone thinks that
woul be useful, but after writing the man page I realized that it's a
fairly niche case, and the hint in the man page is a sufficient
replacement.
2020-09-01 10:48:10 +02:00
Zbigniew Jędrzejewski-Szmek 4fcc9c4962 userdb: fix typo 2020-09-01 10:48:10 +02:00
Zbigniew Jędrzejewski-Szmek c4651e3156 userdbctl: add forgotten --output mode in help 2020-09-01 10:48:10 +02:00
Zbigniew Jędrzejewski-Szmek 52d3fbc83f shared: merge {user,group}-record-show.[ch]
It is natural to include both, and in total they declared three
functions. Let's merge them for simplicity.
2020-09-01 10:48:03 +02:00
Zbigniew Jędrzejewski-Szmek 31be0e9e00 basic/escape: use consistent location for "*" in function declarations
I think it's nicer to move it to the left, since the function
is already a pointer by itself, and it just happens to return a pointer,
and the two concepts are completely separate.
2020-08-31 21:34:01 +02:00
Zbigniew Jędrzejewski-Szmek 8f796e40a5 shared/{user,group}-record-nss: adjust filtering of "valid" passwords
We would reject various passwords that glibc accepts, for example ""
or any descrypted password. Accounts with empty password are definitely
useful, for example for testing or in scenarios where a password is not
needed. Also, using weak encryption methods is probably not a good idea,
it's not the job of our nss helpers to decide that: they should just
faithfully forward whatever data is there.

Also rename the function to make it more obvious that the returned answer
is not in any way certain.
2020-08-31 21:33:16 +02:00
48 changed files with 740 additions and 532 deletions

3
TODO
View File

@ -4,6 +4,9 @@ Bugfixes:
manager or system manager can be always set. It would be better to reject manager or system manager can be always set. It would be better to reject
them when parsing config. them when parsing config.
* userdbctl: "Password OK: yes" is shown even when there are no passwords
or the password is locked.
External: External:
* Fedora: add an rpmlint check that verifies that all unit files in the RPM are listed in %systemd_post macros. * Fedora: add an rpmlint check that verifies that all unit files in the RPM are listed in %systemd_post macros.

View File

@ -415,6 +415,7 @@ Most socket unit settings are available to transient units.
✓ SocketMode= ✓ SocketMode=
✓ DirectoryMode= ✓ DirectoryMode=
✓ Accept= ✓ Accept=
✓ FlushPending=
✓ Writable= ✓ Writable=
✓ MaxConnections= ✓ MaxConnections=
✓ MaxConnectionsPerSource= ✓ MaxConnectionsPerSource=

View File

@ -3950,6 +3950,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
@org.freedesktop.DBus.Property.EmitsChangedSignal("false") @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly u NRefused = ...; readonly u NRefused = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false") @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly u FlushPending = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("false")
readonly s FileDescriptorName = '...'; readonly s FileDescriptorName = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const") @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly i SocketProtocol = ...; readonly i SocketProtocol = ...;
@ -5031,6 +5033,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
<variablelist class="dbus-property" generated="True" extra-ref="NRefused"/> <variablelist class="dbus-property" generated="True" extra-ref="NRefused"/>
<variablelist class="dbus-property" generated="True" extra-ref="FlushPending"/>
<variablelist class="dbus-property" generated="True" extra-ref="FileDescriptorName"/> <variablelist class="dbus-property" generated="True" extra-ref="FileDescriptorName"/>
<variablelist class="dbus-property" generated="True" extra-ref="SocketProtocol"/> <variablelist class="dbus-property" generated="True" extra-ref="SocketProtocol"/>
@ -5508,6 +5512,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
meaning as they have for the corresponding field of service units (see above). In addition to that, meaning as they have for the corresponding field of service units (see above). In addition to that,
the value <literal>service-failed-permanent</literal> indicates that the service of this socket failed the value <literal>service-failed-permanent</literal> indicates that the service of this socket failed
continuously.</para> continuously.</para>
<para><varname>FlushPending</varname> specifies whether to flush the socket
just before entering the listening state. This setting only applies to sockets with
<varname>Accept=</varname> set to <literal>no</literal>.</para>
</refsect2> </refsect2>
</refsect1> </refsect1>

View File

@ -427,6 +427,18 @@
false, in read-only mode. Defaults to false.</para></listitem> false, in read-only mode. Defaults to false.</para></listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><varname>FlushPending=</varname></term>
<listitem><para>Takes a boolean argument. May only be used when
<option>Accept=no</option>. If yes, the socket's buffers are cleared after the
triggered service exited. This causes any pending data to be
flushed and any pending incoming connections to be rejected. If no, the
socket's buffers won't be cleared, permitting the service to handle any
pending connections after restart, which is the usually expected behaviour.
Defaults to <option>no</option>.
</para></listitem>
</varlistentry>
<varlistentry> <varlistentry>
<term><varname>MaxConnections=</varname></term> <term><varname>MaxConnections=</varname></term>
<listitem><para>The maximum number of connections to <listitem><para>The maximum number of connections to

View File

@ -59,7 +59,13 @@
user friendly, human readable output is generated; if <literal>table</literal> a minimal, tabular user friendly, human readable output is generated; if <literal>table</literal> a minimal, tabular
output is generated; if <literal>json</literal> a JSON formatted output is generated. Defaults to output is generated; if <literal>json</literal> a JSON formatted output is generated. Defaults to
<literal>friendly</literal> if a user/group is specified on the command line, <literal>friendly</literal> if a user/group is specified on the command line,
<literal>table</literal> otherwise.</para></listitem> <literal>table</literal> otherwise.</para>
<para>Note that most output formats do not show all available information. In particular,
<literal>classic</literal> and <literal>table</literal> show only the most important fields. Various
modes also do not show password hashes. Use <literal>json</literal> to view all fields, including
any authentication fields.</para>
</listitem>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>

View File

@ -161,28 +161,21 @@ int capability_ambient_set_apply(uint64_t set, bool also_inherit) {
return 0; return 0;
} }
int capability_bounding_set_drop(uint64_t keep, bool right_now) { int capability_gain_cap_setpcap(cap_t *ret_before_caps) {
_cleanup_cap_free_ cap_t before_cap = NULL, after_cap = NULL; _cleanup_cap_free_ cap_t caps = NULL;
cap_flag_value_t fv; cap_flag_value_t fv;
int r; caps = cap_get_proc();
if (!caps)
/* If we are run as PID 1 we will lack CAP_SETPCAP by default
* in the effective set (yes, the kernel drops that when
* executing init!), so get it back temporarily so that we can
* call PR_CAPBSET_DROP. */
before_cap = cap_get_proc();
if (!before_cap)
return -errno; return -errno;
if (cap_get_flag(before_cap, CAP_SETPCAP, CAP_EFFECTIVE, &fv) < 0) if (cap_get_flag(caps, CAP_SETPCAP, CAP_EFFECTIVE, &fv) < 0)
return -errno; return -errno;
if (fv != CAP_SET) { if (fv != CAP_SET) {
_cleanup_cap_free_ cap_t temp_cap = NULL; _cleanup_cap_free_ cap_t temp_cap = NULL;
static const cap_value_t v = CAP_SETPCAP; static const cap_value_t v = CAP_SETPCAP;
temp_cap = cap_dup(before_cap); temp_cap = cap_dup(caps);
if (!temp_cap) if (!temp_cap)
return -errno; return -errno;
@ -193,8 +186,27 @@ int capability_bounding_set_drop(uint64_t keep, bool right_now) {
log_debug_errno(errno, "Can't acquire effective CAP_SETPCAP bit, ignoring: %m"); log_debug_errno(errno, "Can't acquire effective CAP_SETPCAP bit, ignoring: %m");
/* If we didn't manage to acquire the CAP_SETPCAP bit, we continue anyway, after all this just means /* If we didn't manage to acquire the CAP_SETPCAP bit, we continue anyway, after all this just means
* we'll fail later, when we actually intend to drop some capabilities. */ * we'll fail later, when we actually intend to drop some capabilities or try to set securebits. */
} }
if (ret_before_caps)
/* Return the capabilities as they have been before setting CAP_SETPCAP */
*ret_before_caps = TAKE_PTR(caps);
return 0;
}
int capability_bounding_set_drop(uint64_t keep, bool right_now) {
_cleanup_cap_free_ cap_t before_cap = NULL, after_cap = NULL;
int r;
/* If we are run as PID 1 we will lack CAP_SETPCAP by default
* in the effective set (yes, the kernel drops that when
* executing init!), so get it back temporarily so that we can
* call PR_CAPBSET_DROP. */
r = capability_gain_cap_setpcap(&before_cap);
if (r < 0)
return r;
after_cap = cap_dup(before_cap); after_cap = cap_dup(before_cap);
if (!after_cap) if (!after_cap)

View File

@ -14,6 +14,7 @@
unsigned cap_last_cap(void); unsigned cap_last_cap(void);
int have_effective_cap(int value); int have_effective_cap(int value);
int capability_gain_cap_setpcap(cap_t *return_caps);
int capability_bounding_set_drop(uint64_t keep, bool right_now); int capability_bounding_set_drop(uint64_t keep, bool right_now);
int capability_bounding_set_drop_usermode(uint64_t keep); int capability_bounding_set_drop_usermode(uint64_t keep);

View File

@ -730,10 +730,6 @@ bool _hashmap_iterate(HashmapBase *h, Iterator *i, void **value, const void **ke
return true; return true;
} }
bool set_iterate(const Set *s, Iterator *i, void **value) {
return _hashmap_iterate(HASHMAP_BASE((Set*) s), i, value, NULL);
}
#define HASHMAP_FOREACH_IDX(idx, h, i) \ #define HASHMAP_FOREACH_IDX(idx, h, i) \
for ((i) = ITERATOR_FIRST, (idx) = hashmap_iterate_entry((h), &(i)); \ for ((i) = ITERATOR_FIRST, (idx) = hashmap_iterate_entry((h), &(i)); \
(idx != IDX_NIL); \ (idx != IDX_NIL); \
@ -848,6 +844,16 @@ int _set_ensure_allocated(Set **s, const struct hash_ops *hash_ops HASHMAP_DEBU
return hashmap_base_ensure_allocated((HashmapBase**)s, hash_ops, HASHMAP_TYPE_SET HASHMAP_DEBUG_PASS_ARGS); return hashmap_base_ensure_allocated((HashmapBase**)s, hash_ops, HASHMAP_TYPE_SET HASHMAP_DEBUG_PASS_ARGS);
} }
int _ordered_hashmap_ensure_put(OrderedHashmap **h, const struct hash_ops *hash_ops, const void *key, void *value HASHMAP_DEBUG_PARAMS) {
int r;
r = _ordered_hashmap_ensure_allocated(h, hash_ops HASHMAP_DEBUG_PASS_ARGS);
if (r < 0)
return r;
return ordered_hashmap_put(*h, key, value);
}
static void hashmap_free_no_clear(HashmapBase *h) { static void hashmap_free_no_clear(HashmapBase *h) {
assert(!h->has_indirect); assert(!h->has_indirect);
assert(h->n_direct_entries == 0); assert(h->n_direct_entries == 0);

View File

@ -137,6 +137,9 @@ int _ordered_hashmap_ensure_allocated(OrderedHashmap **h, const struct hash_ops
#define hashmap_ensure_allocated(h, ops) _hashmap_ensure_allocated(h, ops HASHMAP_DEBUG_SRC_ARGS) #define hashmap_ensure_allocated(h, ops) _hashmap_ensure_allocated(h, ops HASHMAP_DEBUG_SRC_ARGS)
#define ordered_hashmap_ensure_allocated(h, ops) _ordered_hashmap_ensure_allocated(h, ops HASHMAP_DEBUG_SRC_ARGS) #define ordered_hashmap_ensure_allocated(h, ops) _ordered_hashmap_ensure_allocated(h, ops HASHMAP_DEBUG_SRC_ARGS)
int _ordered_hashmap_ensure_put(OrderedHashmap **h, const struct hash_ops *hash_ops, const void *key, void *value HASHMAP_DEBUG_PARAMS);
#define ordered_hashmap_ensure_put(s, ops, key, value) _ordered_hashmap_ensure_put(s, ops, key, value HASHMAP_DEBUG_SRC_ARGS)
IteratedCache* _hashmap_iterated_cache_new(HashmapBase *h); IteratedCache* _hashmap_iterated_cache_new(HashmapBase *h);
static inline IteratedCache* hashmap_iterated_cache_new(Hashmap *h) { static inline IteratedCache* hashmap_iterated_cache_new(Hashmap *h) {
return (IteratedCache*) _hashmap_iterated_cache_new(HASHMAP_BASE(h)); return (IteratedCache*) _hashmap_iterated_cache_new(HASHMAP_BASE(h));

View File

@ -5,6 +5,7 @@
#include <unistd.h> #include <unistd.h>
#include "btrfs-util.h" #include "btrfs-util.h"
#include "fs-util.h"
#include "label.h" #include "label.h"
#include "macro.h" #include "macro.h"
#include "selinux-util.h" #include "selinux-util.h"
@ -45,6 +46,27 @@ int symlink_label(const char *old_path, const char *new_path) {
return mac_smack_fix(new_path, 0); return mac_smack_fix(new_path, 0);
} }
int symlink_atomic_label(const char *from, const char *to) {
int r;
assert(from);
assert(to);
r = mac_selinux_create_file_prepare(to, S_IFLNK);
if (r < 0)
return r;
if (symlink_atomic(from, to) < 0)
r = -errno;
mac_selinux_create_file_clear();
if (r < 0)
return r;
return mac_smack_fix(to, 0);
}
int mknod_label(const char *pathname, mode_t mode, dev_t dev) { int mknod_label(const char *pathname, mode_t mode, dev_t dev) {
int r; int r;

View File

@ -17,6 +17,7 @@ static inline int label_fix(const char *path, LabelFixFlags flags) {
int mkdir_label(const char *path, mode_t mode); int mkdir_label(const char *path, mode_t mode);
int mkdirat_label(int dirfd, const char *path, mode_t mode); int mkdirat_label(int dirfd, const char *path, mode_t mode);
int symlink_label(const char *old_path, const char *new_path); int symlink_label(const char *old_path, const char *new_path);
int symlink_atomic_label(const char *from, const char *to);
int mknod_label(const char *pathname, mode_t mode, dev_t dev); int mknod_label(const char *pathname, mode_t mode, dev_t dev);
int btrfs_subvol_make_label(const char *path); int btrfs_subvol_make_label(const char *path);

View File

@ -4,6 +4,27 @@
#include "ordered-set.h" #include "ordered-set.h"
#include "strv.h" #include "strv.h"
int _ordered_set_ensure_allocated(OrderedSet **s, const struct hash_ops *ops HASHMAP_DEBUG_PARAMS) {
if (*s)
return 0;
*s = _ordered_set_new(ops HASHMAP_DEBUG_PASS_ARGS);
if (!*s)
return -ENOMEM;
return 0;
}
int _ordered_set_ensure_put(OrderedSet **s, const struct hash_ops *ops, void *p HASHMAP_DEBUG_PARAMS) {
int r;
r = _ordered_set_ensure_allocated(s, ops HASHMAP_DEBUG_PASS_ARGS);
if (r < 0)
return r;
return ordered_set_put(*s, p);
}
int ordered_set_consume(OrderedSet *s, void *p) { int ordered_set_consume(OrderedSet *s, void *p) {
int r; int r;

View File

@ -7,20 +7,16 @@
typedef struct OrderedSet OrderedSet; typedef struct OrderedSet OrderedSet;
static inline OrderedSet* ordered_set_new(const struct hash_ops *ops) { static inline OrderedSet* _ordered_set_new(const struct hash_ops *ops HASHMAP_DEBUG_PARAMS) {
return (OrderedSet*) ordered_hashmap_new(ops); return (OrderedSet*) _ordered_hashmap_new(ops HASHMAP_DEBUG_PASS_ARGS);
} }
#define ordered_set_new(ops) _ordered_set_new(ops HASHMAP_DEBUG_SRC_ARGS)
static inline int ordered_set_ensure_allocated(OrderedSet **s, const struct hash_ops *ops) { int _ordered_set_ensure_allocated(OrderedSet **s, const struct hash_ops *ops HASHMAP_DEBUG_PARAMS);
if (*s) #define ordered_set_ensure_allocated(s, ops) _ordered_set_ensure_allocated(s, ops HASHMAP_DEBUG_SRC_ARGS)
return 0;
*s = ordered_set_new(ops); int _ordered_set_ensure_put(OrderedSet **s, const struct hash_ops *ops, void *p HASHMAP_DEBUG_PARAMS);
if (!*s) #define ordered_set_ensure_put(s, hash_ops, key) _ordered_set_ensure_put(s, hash_ops, key HASHMAP_DEBUG_SRC_ARGS)
return -ENOMEM;
return 0;
}
static inline OrderedSet* ordered_set_free(OrderedSet *s) { static inline OrderedSet* ordered_set_free(OrderedSet *s) {
return (OrderedSet*) ordered_hashmap_free((OrderedHashmap*) s); return (OrderedSet*) ordered_hashmap_free((OrderedHashmap*) s);

View File

@ -77,7 +77,9 @@ static inline unsigned set_buckets(const Set *s) {
return _hashmap_buckets(HASHMAP_BASE((Set *) s)); return _hashmap_buckets(HASHMAP_BASE((Set *) s));
} }
bool set_iterate(const Set *s, Iterator *i, void **value); static inline bool set_iterate(const Set *s, Iterator *i, void **value) {
return _hashmap_iterate(HASHMAP_BASE((Set*) s), i, value, NULL);
}
static inline void set_clear(Set *s) { static inline void set_clear(Set *s) {
_hashmap_clear(HASHMAP_BASE(s), NULL, NULL); _hashmap_clear(HASHMAP_BASE(s), NULL, NULL);

View File

@ -150,18 +150,22 @@ bool utf8_is_printable_newline(const char* str, size_t length, bool allow_newlin
return true; return true;
} }
char *utf8_is_valid(const char *str) { char *utf8_is_valid_n(const char *str, size_t len_bytes) {
const char *p; /* Check if the string is composed of valid utf8 characters. If length len_bytes is given, stop after
* len_bytes. Otherwise, stop at NUL. */
assert(str); assert(str);
p = str; for (const char *p = str; len_bytes != (size_t) -1 ? (size_t) (p - str) < len_bytes : *p != '\0'; ) {
while (*p) {
int len; int len;
len = utf8_encoded_valid_unichar(p, (size_t) -1); if (_unlikely_(*p == '\0') && len_bytes != (size_t) -1)
if (len < 0) return NULL; /* embedded NUL */
return NULL;
len = utf8_encoded_valid_unichar(p,
len_bytes != (size_t) -1 ? len_bytes - (p - str) : (size_t) -1);
if (_unlikely_(len < 0))
return NULL; /* invalid character */
p += len; p += len;
} }

View File

@ -14,7 +14,10 @@
bool unichar_is_valid(char32_t c); bool unichar_is_valid(char32_t c);
char *utf8_is_valid(const char *s) _pure_; char *utf8_is_valid_n(const char *str, size_t len_bytes) _pure_;
static inline char *utf8_is_valid(const char *s) {
return utf8_is_valid_n(s, (size_t) -1);
}
char *ascii_is_valid(const char *s) _pure_; char *ascii_is_valid(const char *s) _pure_;
char *ascii_is_valid_n(const char *str, size_t len); char *ascii_is_valid_n(const char *str, size_t len);

View File

@ -291,7 +291,7 @@ int manager_varlink_init(Manager *m) {
return log_error_errno(r, "Failed to register varlink methods: %m"); return log_error_errno(r, "Failed to register varlink methods: %m");
if (!MANAGER_IS_TEST_RUN(m)) { if (!MANAGER_IS_TEST_RUN(m)) {
(void) mkdir_p("/run/systemd/userdb", 0755); (void) mkdir_p_label("/run/systemd/userdb", 0755);
r = varlink_server_listen_address(s, "/run/systemd/userdb/io.systemd.DynamicUser", 0666); r = varlink_server_listen_address(s, "/run/systemd/userdb/io.systemd.DynamicUser", 0666);
if (r < 0) if (r < 0)

View File

@ -86,6 +86,7 @@ const sd_bus_vtable bus_socket_vtable[] = {
SD_BUS_PROPERTY("SocketMode", "u", bus_property_get_mode, offsetof(Socket, socket_mode), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("SocketMode", "u", bus_property_get_mode, offsetof(Socket, socket_mode), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("DirectoryMode", "u", bus_property_get_mode, offsetof(Socket, directory_mode), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("DirectoryMode", "u", bus_property_get_mode, offsetof(Socket, directory_mode), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("Accept", "b", bus_property_get_bool, offsetof(Socket, accept), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Accept", "b", bus_property_get_bool, offsetof(Socket, accept), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("FlushPending", "b", bus_property_get_bool, offsetof(Socket, flush_pending), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("Writable", "b", bus_property_get_bool, offsetof(Socket, writable), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Writable", "b", bus_property_get_bool, offsetof(Socket, writable), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("KeepAlive", "b", bus_property_get_bool, offsetof(Socket, keep_alive), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("KeepAlive", "b", bus_property_get_bool, offsetof(Socket, keep_alive), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("KeepAliveTimeUSec", "t", bus_property_get_usec, offsetof(Socket, keep_alive_time), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("KeepAliveTimeUSec", "t", bus_property_get_usec, offsetof(Socket, keep_alive_time), SD_BUS_VTABLE_PROPERTY_CONST),
@ -179,6 +180,9 @@ static int bus_socket_set_transient_property(
if (streq(name, "Accept")) if (streq(name, "Accept"))
return bus_set_transient_bool(u, name, &s->accept, message, flags, error); return bus_set_transient_bool(u, name, &s->accept, message, flags, error);
if (streq(name, "FlushPending"))
return bus_set_transient_bool(u, name, &s->flush_pending, message, flags, error);
if (streq(name, "Writable")) if (streq(name, "Writable"))
return bus_set_transient_bool(u, name, &s->writable, message, flags, error); return bus_set_transient_bool(u, name, &s->writable, message, flags, error);

View File

@ -1077,26 +1077,42 @@ static int enforce_groups(gid_t gid, const gid_t *supplementary_gids, int ngids)
return 0; return 0;
} }
static int set_securebits(int bits, int mask) {
int current, applied;
current = prctl(PR_GET_SECUREBITS);
if (current < 0)
return -errno;
/* Clear all securebits defined in mask and set bits */
applied = (current & ~mask) | bits;
if (current == applied)
return 0;
if (prctl(PR_SET_SECUREBITS, applied) < 0)
return -errno;
return 1;
}
static int enforce_user(const ExecContext *context, uid_t uid) { static int enforce_user(const ExecContext *context, uid_t uid) {
assert(context); assert(context);
int r;
if (!uid_is_valid(uid)) if (!uid_is_valid(uid))
return 0; return 0;
/* Sets (but doesn't look up) the uid and make sure we keep the /* Sets (but doesn't look up) the uid and make sure we keep the
* capabilities while doing so. */ * capabilities while doing so. For setting secure bits the capability CAP_SETPCAP is
* required, so we also need keep-caps in this case.
*/
if (context->capability_ambient_set != 0) { if (context->capability_ambient_set != 0 || context->secure_bits != 0) {
/* First step: If we need to keep capabilities but /* First step: If we need to keep capabilities but
* drop privileges we need to make sure we keep our * drop privileges we need to make sure we keep our
* caps, while we drop privileges. */ * caps, while we drop privileges. */
if (uid != 0) { if (uid != 0) {
int sb = context->secure_bits | 1<<SECURE_KEEP_CAPS; /* Add KEEP_CAPS to the securebits */
r = set_securebits(1<<SECURE_KEEP_CAPS, 0);
if (prctl(PR_GET_SECUREBITS) != sb) if (r < 0)
if (prctl(PR_SET_SECUREBITS, sb) < 0) return r;
return -errno;
} }
} }
@ -4337,12 +4353,27 @@ static int exec_child(
#endif #endif
/* PR_GET_SECUREBITS is not privileged, while PR_SET_SECUREBITS is. So to suppress potential EPERMs /* PR_GET_SECUREBITS is not privileged, while PR_SET_SECUREBITS is. So to suppress potential EPERMs
* we'll try not to call PR_SET_SECUREBITS unless necessary. */ * we'll try not to call PR_SET_SECUREBITS unless necessary. Setting securebits requires
if (prctl(PR_GET_SECUREBITS) != secure_bits) * CAP_SETPCAP. */
if (prctl(PR_GET_SECUREBITS) != secure_bits) {
/* CAP_SETPCAP is required to set securebits. This capabilitiy is raised into the
* effective set here.
* The effective set is overwritten during execve with the following values:
* - ambient set (for non-root processes)
* - (inheritable | bounding) set for root processes)
*
* Hence there is no security impact to raise it in the effective set before execve
*/
r = capability_gain_cap_setpcap(NULL);
if (r < 0) {
*exit_status = EXIT_CAPABILITIES;
return log_unit_error_errno(unit, r, "Failed to gain CAP_SETPCAP for setting secure bits");
}
if (prctl(PR_SET_SECUREBITS, secure_bits) < 0) { if (prctl(PR_SET_SECUREBITS, secure_bits) < 0) {
*exit_status = EXIT_SECUREBITS; *exit_status = EXIT_SECUREBITS;
return log_unit_error_errno(unit, errno, "Failed to set process secure bits: %m"); return log_unit_error_errno(unit, errno, "Failed to set process secure bits: %m");
} }
}
if (context_has_no_new_privileges(context)) if (context_has_no_new_privileges(context))
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) { if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) {

View File

@ -391,6 +391,7 @@ Socket.SocketGroup, config_parse_user_group_compat, 0,
Socket.SocketMode, config_parse_mode, 0, offsetof(Socket, socket_mode) Socket.SocketMode, config_parse_mode, 0, offsetof(Socket, socket_mode)
Socket.DirectoryMode, config_parse_mode, 0, offsetof(Socket, directory_mode) Socket.DirectoryMode, config_parse_mode, 0, offsetof(Socket, directory_mode)
Socket.Accept, config_parse_bool, 0, offsetof(Socket, accept) Socket.Accept, config_parse_bool, 0, offsetof(Socket, accept)
Socket.FlushPending, config_parse_bool, 0, offsetof(Socket, flush_pending)
Socket.Writable, config_parse_bool, 0, offsetof(Socket, writable) Socket.Writable, config_parse_bool, 0, offsetof(Socket, writable)
Socket.MaxConnections, config_parse_unsigned, 0, offsetof(Socket, max_connections) Socket.MaxConnections, config_parse_unsigned, 0, offsetof(Socket, max_connections)
Socket.MaxConnectionsPerSource, config_parse_unsigned, 0, offsetof(Socket, max_connections_per_source) Socket.MaxConnectionsPerSource, config_parse_unsigned, 0, offsetof(Socket, max_connections_per_source)

View File

@ -63,6 +63,7 @@
#include "ratelimit.h" #include "ratelimit.h"
#include "rlimit-util.h" #include "rlimit-util.h"
#include "rm-rf.h" #include "rm-rf.h"
#include "selinux-util.h"
#include "serialize.h" #include "serialize.h"
#include "signal-util.h" #include "signal-util.h"
#include "socket-util.h" #include "socket-util.h"
@ -963,9 +964,9 @@ static int manager_setup_notify(Manager *m) {
(void) mkdir_parents_label(m->notify_socket, 0755); (void) mkdir_parents_label(m->notify_socket, 0755);
(void) sockaddr_un_unlink(&sa.un); (void) sockaddr_un_unlink(&sa.un);
r = bind(fd, &sa.sa, sa_len); r = mac_selinux_bind(fd, &sa.sa, sa_len);
if (r < 0) if (r < 0)
return log_error_errno(errno, "bind(%s) failed: %m", m->notify_socket); return log_error_errno(r, "bind(%s) failed: %m", m->notify_socket);
r = setsockopt_int(fd, SOL_SOCKET, SO_PASSCRED, true); r = setsockopt_int(fd, SOL_SOCKET, SO_PASSCRED, true);
if (r < 0) if (r < 0)

View File

@ -72,6 +72,7 @@ static const UnitActiveState state_translation_table[_SOCKET_STATE_MAX] = {
static int socket_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata); static int socket_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata);
static int socket_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata); static int socket_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata);
static void flush_ports(Socket *s);
static void socket_init(Unit *u) { static void socket_init(Unit *u) {
Socket *s = SOCKET(u); Socket *s = SOCKET(u);
@ -669,6 +670,11 @@ static void socket_dump(Unit *u, FILE *f, const char *prefix) {
prefix, s->n_connections, prefix, s->n_connections,
prefix, s->max_connections, prefix, s->max_connections,
prefix, s->max_connections_per_source); prefix, s->max_connections_per_source);
else
fprintf(f,
"%sFlushPending: %s\n",
prefix, yes_no(s->flush_pending));
if (s->priority >= 0) if (s->priority >= 0)
fprintf(f, fprintf(f,
@ -2201,6 +2207,11 @@ static void socket_enter_listening(Socket *s) {
int r; int r;
assert(s); assert(s);
if (!s->accept && s->flush_pending) {
log_unit_debug(UNIT(s), "Flushing socket before listening.");
flush_ports(s);
}
r = socket_watch_fds(s); r = socket_watch_fds(s);
if (r < 0) { if (r < 0) {
log_unit_warning_errno(UNIT(s), r, "Failed to watch sockets: %m"); log_unit_warning_errno(UNIT(s), r, "Failed to watch sockets: %m");

View File

@ -110,6 +110,7 @@ struct Socket {
bool accept; bool accept;
bool remove_on_stop; bool remove_on_stop;
bool writable; bool writable;
bool flush_pending;
int socket_protocol; int socket_protocol;

View File

@ -24,10 +24,10 @@
#include "fileio-label.h" #include "fileio-label.h"
#include "fileio.h" #include "fileio.h"
#include "format-util.h" #include "format-util.h"
#include "fs-util.h"
#include "id128-util.h" #include "id128-util.h"
#include "io-util.h" #include "io-util.h"
#include "install.h" #include "install.h"
#include "label.h"
#include "load-dropin.h" #include "load-dropin.h"
#include "load-fragment.h" #include "load-fragment.h"
#include "log.h" #include "log.h"
@ -5603,7 +5603,7 @@ static int unit_export_invocation_id(Unit *u) {
if (r < 0) if (r < 0)
return log_unit_debug_errno(u, r, "Failed to get invocation path: %m"); return log_unit_debug_errno(u, r, "Failed to get invocation path: %m");
r = symlink_atomic(u->invocation_id_string, p); r = symlink_atomic_label(u->invocation_id_string, p);
if (r < 0) if (r < 0)
return log_unit_debug_errno(u, r, "Failed to create invocation ID symlink %s: %m", p); return log_unit_debug_errno(u, r, "Failed to create invocation ID symlink %s: %m", p);

View File

@ -6,13 +6,13 @@
#include "env-util.h" #include "env-util.h"
#include "errno-util.h" #include "errno-util.h"
#include "fd-util.h" #include "fd-util.h"
#include "group-record-nss.h"
#include "macro.h" #include "macro.h"
#include "nss-systemd.h" #include "nss-systemd.h"
#include "nss-util.h" #include "nss-util.h"
#include "pthread-util.h" #include "pthread-util.h"
#include "signal-util.h" #include "signal-util.h"
#include "strv.h" #include "strv.h"
#include "user-record-nss.h"
#include "user-util.h" #include "user-util.h"
#include "userdb-glue.h" #include "userdb-glue.h"
#include "userdb.h" #include "userdb.h"

View File

@ -2,9 +2,9 @@
#include "env-util.h" #include "env-util.h"
#include "fd-util.h" #include "fd-util.h"
#include "group-record-nss.h"
#include "nss-systemd.h" #include "nss-systemd.h"
#include "strv.h" #include "strv.h"
#include "user-record-nss.h"
#include "user-record.h" #include "user-record.h"
#include "userdb-glue.h" #include "userdb-glue.h"
#include "userdb.h" #include "userdb.h"

View File

@ -1956,6 +1956,7 @@ static int bus_append_socket_property(sd_bus_message *m, const char *field, cons
int r; int r;
if (STR_IN_SET(field, "Accept", if (STR_IN_SET(field, "Accept",
"FlushPending",
"Writable", "Writable",
"KeepAlive", "KeepAlive",
"NoDelay", "NoDelay",

View File

@ -1,219 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "errno-util.h"
#include "group-record-nss.h"
#include "libcrypt-util.h"
#include "strv.h"
int nss_group_to_group_record(
const struct group *grp,
const struct sgrp *sgrp,
GroupRecord **ret) {
_cleanup_(group_record_unrefp) GroupRecord *g = NULL;
int r;
assert(grp);
assert(ret);
if (isempty(grp->gr_name))
return -EINVAL;
if (sgrp && !streq_ptr(sgrp->sg_namp, grp->gr_name))
return -EINVAL;
g = group_record_new();
if (!g)
return -ENOMEM;
g->group_name = strdup(grp->gr_name);
if (!g->group_name)
return -ENOMEM;
g->members = strv_copy(grp->gr_mem);
if (!g->members)
return -ENOMEM;
g->gid = grp->gr_gid;
if (sgrp) {
if (hashed_password_valid(sgrp->sg_passwd)) {
g->hashed_password = strv_new(sgrp->sg_passwd);
if (!g->hashed_password)
return -ENOMEM;
}
r = strv_extend_strv(&g->members, sgrp->sg_mem, 1);
if (r < 0)
return r;
g->administrators = strv_copy(sgrp->sg_adm);
if (!g->administrators)
return -ENOMEM;
}
r = json_build(&g->json, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(g->group_name)),
JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(g->gid)),
JSON_BUILD_PAIR_CONDITION(!strv_isempty(g->members), "members", JSON_BUILD_STRV(g->members)),
JSON_BUILD_PAIR_CONDITION(!strv_isempty(g->hashed_password), "privileged", JSON_BUILD_OBJECT(JSON_BUILD_PAIR("hashedPassword", JSON_BUILD_STRV(g->hashed_password)))),
JSON_BUILD_PAIR_CONDITION(!strv_isempty(g->administrators), "administrators", JSON_BUILD_STRV(g->administrators))));
if (r < 0)
return r;
g->mask = USER_RECORD_REGULAR |
(!strv_isempty(g->hashed_password) ? USER_RECORD_PRIVILEGED : 0);
*ret = TAKE_PTR(g);
return 0;
}
int nss_sgrp_for_group(const struct group *grp, struct sgrp *ret_sgrp, char **ret_buffer) {
size_t buflen = 4096;
int r;
assert(grp);
assert(ret_sgrp);
assert(ret_buffer);
for (;;) {
_cleanup_free_ char *buf = NULL;
struct sgrp sgrp, *result;
buf = malloc(buflen);
if (!buf)
return -ENOMEM;
r = getsgnam_r(grp->gr_name, &sgrp, buf, buflen, &result);
if (r == 0) {
if (!result)
return -ESRCH;
*ret_sgrp = *result;
*ret_buffer = TAKE_PTR(buf);
return 0;
}
if (r < 0)
return -EIO; /* Weird, this should not return negative! */
if (r != ERANGE)
return -r;
if (buflen > SIZE_MAX / 2)
return -ERANGE;
buflen *= 2;
buf = mfree(buf);
}
}
int nss_group_record_by_name(
const char *name,
bool with_shadow,
GroupRecord **ret) {
_cleanup_free_ char *buf = NULL, *sbuf = NULL;
struct group grp, *result;
bool incomplete = false;
size_t buflen = 4096;
struct sgrp sgrp, *sresult = NULL;
int r;
assert(name);
assert(ret);
for (;;) {
buf = malloc(buflen);
if (!buf)
return -ENOMEM;
r = getgrnam_r(name, &grp, buf, buflen, &result);
if (r == 0) {
if (!result)
return -ESRCH;
break;
}
if (r < 0)
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getgrnam_r() returned a negative value");
if (r != ERANGE)
return -r;
if (buflen > SIZE_MAX / 2)
return -ERANGE;
buflen *= 2;
buf = mfree(buf);
}
if (with_shadow) {
r = nss_sgrp_for_group(result, &sgrp, &sbuf);
if (r < 0) {
log_debug_errno(r, "Failed to do shadow lookup for group %s, ignoring: %m", result->gr_name);
incomplete = ERRNO_IS_PRIVILEGE(r);
} else
sresult = &sgrp;
} else
incomplete = true;
r = nss_group_to_group_record(result, sresult, ret);
if (r < 0)
return r;
(*ret)->incomplete = incomplete;
return 0;
}
int nss_group_record_by_gid(
gid_t gid,
bool with_shadow,
GroupRecord **ret) {
_cleanup_free_ char *buf = NULL, *sbuf = NULL;
struct group grp, *result;
bool incomplete = false;
size_t buflen = 4096;
struct sgrp sgrp, *sresult = NULL;
int r;
assert(ret);
for (;;) {
buf = malloc(buflen);
if (!buf)
return -ENOMEM;
r = getgrgid_r(gid, &grp, buf, buflen, &result);
if (r == 0) {
if (!result)
return -ESRCH;
break;
}
if (r < 0)
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getgrgid_r() returned a negative value");
if (r != ERANGE)
return -r;
if (buflen > SIZE_MAX / 2)
return -ERANGE;
buflen *= 2;
buf = mfree(buf);
}
if (with_shadow) {
r = nss_sgrp_for_group(result, &sgrp, &sbuf);
if (r < 0) {
log_debug_errno(r, "Failed to do shadow lookup for group %s, ignoring: %m", result->gr_name);
incomplete = ERRNO_IS_PRIVILEGE(r);
} else
sresult = &sgrp;
} else
incomplete = true;
r = nss_group_to_group_record(result, sresult, ret);
if (r < 0)
return r;
(*ret)->incomplete = incomplete;
return 0;
}

View File

@ -1,15 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
#include <grp.h>
#include <gshadow.h>
#include "group-record.h"
/* Synthesize GroupRecord objects from NSS data */
int nss_group_to_group_record(const struct group *grp, const struct sgrp *sgrp, GroupRecord **ret);
int nss_sgrp_for_group(const struct group *grp, struct sgrp *ret_sgrp, char **ret_buffer);
int nss_group_record_by_name(const char *name, bool with_shadow, GroupRecord **ret);
int nss_group_record_by_gid(gid_t gid, bool with_shadow, GroupRecord **ret);

View File

@ -1,79 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "format-util.h"
#include "group-record-show.h"
#include "strv.h"
#include "user-util.h"
#include "userdb.h"
void group_record_show(GroupRecord *gr, bool show_full_user_info) {
int r;
printf(" Group name: %s\n",
group_record_group_name_and_realm(gr));
printf(" Disposition: %s\n", user_disposition_to_string(group_record_disposition(gr)));
if (gr->last_change_usec != USEC_INFINITY) {
char buf[FORMAT_TIMESTAMP_MAX];
printf(" Last Change: %s\n", format_timestamp(buf, sizeof(buf), gr->last_change_usec));
}
if (gid_is_valid(gr->gid))
printf(" GID: " GID_FMT "\n", gr->gid);
if (show_full_user_info) {
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
r = membershipdb_by_group(gr->group_name, 0, &iterator);
if (r < 0) {
errno = -r;
printf(" Members: (can't acquire: %m)");
} else {
const char *prefix = " Members:";
for (;;) {
_cleanup_free_ char *user = NULL;
r = membershipdb_iterator_get(iterator, &user, NULL);
if (r == -ESRCH)
break;
if (r < 0) {
errno = -r;
printf("%s (can't iterate: %m\n", prefix);
break;
}
printf("%s %s\n", prefix, user);
prefix = " ";
}
}
} else {
const char *prefix = " Members:";
char **i;
STRV_FOREACH(i, gr->members) {
printf("%s %s\n", prefix, *i);
prefix = " ";
}
}
if (!strv_isempty(gr->administrators)) {
const char *prefix = " Admins:";
char **i;
STRV_FOREACH(i, gr->administrators) {
printf("%s %s\n", prefix, *i);
prefix = " ";
}
}
if (gr->description && !streq(gr->description, gr->group_name))
printf(" Description: %s\n", gr->description);
if (!strv_isempty(gr->hashed_password))
printf(" Passwords: %zu\n", strv_length(gr->hashed_password));
if (gr->service)
printf(" Service: %s\n", gr->service);
}

View File

@ -1,6 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
#include "group-record.h"
void group_record_show(GroupRecord *gr, bool show_full_user_info);

View File

@ -405,6 +405,9 @@ int json_variant_new_stringn(JsonVariant **ret, const char *s, size_t n) {
return 0; return 0;
} }
if (!utf8_is_valid_n(s, n)) /* JSON strings must be valid UTF-8 */
return -EUCLEAN;
r = json_variant_new(&v, JSON_VARIANT_STRING, n + 1); r = json_variant_new(&v, JSON_VARIANT_STRING, n + 1);
if (r < 0) if (r < 0)
return r; return r;
@ -636,9 +639,13 @@ int json_variant_new_array_strv(JsonVariant **ret, char **l) {
return r; return r;
w->is_reference = true; w->is_reference = true;
} else } else {
if (!utf8_is_valid_n(l[v->n_elements], k)) /* JSON strings must be valid UTF-8 */
return -EUCLEAN;
memcpy(w->string, l[v->n_elements], k+1); memcpy(w->string, l[v->n_elements], k+1);
} }
}
v->normalized = true; v->normalized = true;
@ -1482,6 +1489,58 @@ static int print_source(FILE *f, JsonVariant *v, JsonFormatFlags flags, bool whi
return 0; return 0;
} }
static void json_format_string(FILE *f, const char *q, JsonFormatFlags flags) {
assert(q);
fputc('"', f);
if (flags & JSON_FORMAT_COLOR)
fputs(ANSI_GREEN, f);
for (; *q; q++)
switch (*q) {
case '"':
fputs("\\\"", f);
break;
case '\\':
fputs("\\\\", f);
break;
case '\b':
fputs("\\b", f);
break;
case '\f':
fputs("\\f", f);
break;
case '\n':
fputs("\\n", f);
break;
case '\r':
fputs("\\r", f);
break;
case '\t':
fputs("\\t", f);
break;
default:
if ((signed char) *q >= 0 && *q < ' ')
fprintf(f, "\\u%04x", *q);
else
fputc(*q, f);
break;
}
if (flags & JSON_FORMAT_COLOR)
fputs(ANSI_NORMAL, f);
fputc('"', f);
}
static int json_format(FILE *f, JsonVariant *v, JsonFormatFlags flags, const char *prefix) { static int json_format(FILE *f, JsonVariant *v, JsonFormatFlags flags, const char *prefix) {
int r; int r;
@ -1554,62 +1613,10 @@ static int json_format(FILE *f, JsonVariant *v, JsonFormatFlags flags, const cha
fputs(ANSI_NORMAL, f); fputs(ANSI_NORMAL, f);
break; break;
case JSON_VARIANT_STRING: { case JSON_VARIANT_STRING:
const char *q; json_format_string(f, json_variant_string(v), flags);
fputc('"', f);
if (flags & JSON_FORMAT_COLOR)
fputs(ANSI_GREEN, f);
for (q = json_variant_string(v); *q; q++) {
switch (*q) {
case '"':
fputs("\\\"", f);
break; break;
case '\\':
fputs("\\\\", f);
break;
case '\b':
fputs("\\b", f);
break;
case '\f':
fputs("\\f", f);
break;
case '\n':
fputs("\\n", f);
break;
case '\r':
fputs("\\r", f);
break;
case '\t':
fputs("\\t", f);
break;
default:
if ((signed char) *q >= 0 && *q < ' ')
fprintf(f, "\\u%04x", *q);
else
fputc(*q, f);
break;
}
}
if (flags & JSON_FORMAT_COLOR)
fputs(ANSI_NORMAL, f);
fputc('"', f);
break;
}
case JSON_VARIANT_ARRAY: { case JSON_VARIANT_ARRAY: {
size_t i, n; size_t i, n;

View File

@ -74,13 +74,18 @@ int make_salt(char **ret) {
#endif #endif
} }
bool hashed_password_valid(const char *s) { bool looks_like_hashed_password(const char *s) {
/* Returns false if the specified string is certainly not a hashed UNIX password. crypt(5) lists
/* Returns true if the specified string is a 'valid' hashed UNIX password, i.e. if starts with '$' or * various hashing methods. We only reject (return false) strings which are documented to have
* with '!$' (the latter being a valid, yet locked password). */ * different meanings.
*
if (isempty(s)) * In particular, we allow locked passwords, i.e. strings starting with "!", including just "!",
* i.e. the locked empty password. See also fc58c0c7bf7e4f525b916e3e5be0de2307fef04e.
*/
if (!s)
return false; return false;
return STARTSWITH_SET(s, "$", "!$"); s += strspn(s, "!"); /* Skip (possibly duplicated) locking prefix */
return !STR_IN_SET(s, "x", "*");
} }

View File

@ -19,4 +19,4 @@
int make_salt(char **ret); int make_salt(char **ret);
bool hashed_password_valid(const char *s); bool looks_like_hashed_password(const char *s);

View File

@ -113,10 +113,6 @@ shared_sources = files('''
geneve-util.h geneve-util.h
gpt.c gpt.c
gpt.h gpt.h
group-record-nss.c
group-record-nss.h
group-record-show.c
group-record-show.h
group-record.c group-record.c
group-record.h group-record.h
id128-print.c id128-print.c

View File

@ -6,10 +6,35 @@
#include "strv.h" #include "strv.h"
#include "user-record-nss.h" #include "user-record-nss.h"
#include "user-util.h" #include "user-util.h"
#include "utf8.h"
#define SET_IF(field, condition, value, fallback) \ #define SET_IF(field, condition, value, fallback) \
field = (condition) ? (value) : (fallback) field = (condition) ? (value) : (fallback)
static inline const char* utf8_only(const char *s) {
return s && utf8_is_valid(s) ? s : NULL;
}
static inline int strv_extend_strv_utf8_only(char ***dst, char **src, bool filter_duplicates) {
_cleanup_free_ char **t = NULL;
size_t l, j = 0;
/* First, do a shallow copy of s, filtering for only valid utf-8 strings */
l = strv_length(src);
t = new(char*, l + 1);
if (!t)
return -ENOMEM;
for (size_t i = 0; i < l; i++)
if (utf8_is_valid(src[i]))
t[j++] = src[i];
if (j == 0)
return 0;
t[j] = NULL;
return strv_extend_strv(dst, t, filter_duplicates);
}
int nss_passwd_to_user_record( int nss_passwd_to_user_record(
const struct passwd *pwd, const struct passwd *pwd,
const struct spwd *spwd, const struct spwd *spwd,
@ -55,18 +80,19 @@ int nss_passwd_to_user_record(
free_and_replace(hr->real_name, mangled); free_and_replace(hr->real_name, mangled);
} }
r = free_and_strdup(&hr->home_directory, empty_to_null(pwd->pw_dir)); r = free_and_strdup(&hr->home_directory, utf8_only(empty_to_null(pwd->pw_dir)));
if (r < 0) if (r < 0)
return r; return r;
r = free_and_strdup(&hr->shell, empty_to_null(pwd->pw_shell)); r = free_and_strdup(&hr->shell, utf8_only(empty_to_null(pwd->pw_shell)));
if (r < 0) if (r < 0)
return r; return r;
hr->uid = pwd->pw_uid; hr->uid = pwd->pw_uid;
hr->gid = pwd->pw_gid; hr->gid = pwd->pw_gid;
if (spwd && hashed_password_valid(spwd->sp_pwdp)) { if (spwd &&
looks_like_hashed_password(utf8_only(spwd->sp_pwdp))) { /* Ignore locked, disabled, and mojibake passwords */
strv_free_erase(hr->hashed_password); strv_free_erase(hr->hashed_password);
hr->hashed_password = strv_new(spwd->sp_pwdp); hr->hashed_password = strv_new(spwd->sp_pwdp);
if (!hr->hashed_password) if (!hr->hashed_password)
@ -290,3 +316,216 @@ int nss_user_record_by_uid(
(*ret)->incomplete = incomplete; (*ret)->incomplete = incomplete;
return 0; return 0;
} }
int nss_group_to_group_record(
const struct group *grp,
const struct sgrp *sgrp,
GroupRecord **ret) {
_cleanup_(group_record_unrefp) GroupRecord *g = NULL;
int r;
assert(grp);
assert(ret);
if (isempty(grp->gr_name))
return -EINVAL;
if (sgrp && !streq_ptr(sgrp->sg_namp, grp->gr_name))
return -EINVAL;
g = group_record_new();
if (!g)
return -ENOMEM;
g->group_name = strdup(grp->gr_name);
if (!g->group_name)
return -ENOMEM;
r = strv_extend_strv_utf8_only(&g->members, grp->gr_mem, false);
if (r < 0)
return r;
g->gid = grp->gr_gid;
if (sgrp) {
if (looks_like_hashed_password(utf8_only(sgrp->sg_passwd))) {
g->hashed_password = strv_new(sgrp->sg_passwd);
if (!g->hashed_password)
return -ENOMEM;
}
r = strv_extend_strv_utf8_only(&g->members, sgrp->sg_mem, true);
if (r < 0)
return r;
r = strv_extend_strv_utf8_only(&g->administrators, sgrp->sg_adm, false);
if (r < 0)
return r;
}
r = json_build(&g->json, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(g->group_name)),
JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(g->gid)),
JSON_BUILD_PAIR_CONDITION(!strv_isempty(g->members), "members", JSON_BUILD_STRV(g->members)),
JSON_BUILD_PAIR_CONDITION(!strv_isempty(g->hashed_password), "privileged", JSON_BUILD_OBJECT(JSON_BUILD_PAIR("hashedPassword", JSON_BUILD_STRV(g->hashed_password)))),
JSON_BUILD_PAIR_CONDITION(!strv_isempty(g->administrators), "administrators", JSON_BUILD_STRV(g->administrators))));
if (r < 0)
return r;
g->mask = USER_RECORD_REGULAR |
(!strv_isempty(g->hashed_password) ? USER_RECORD_PRIVILEGED : 0);
*ret = TAKE_PTR(g);
return 0;
}
int nss_sgrp_for_group(const struct group *grp, struct sgrp *ret_sgrp, char **ret_buffer) {
size_t buflen = 4096;
int r;
assert(grp);
assert(ret_sgrp);
assert(ret_buffer);
for (;;) {
_cleanup_free_ char *buf = NULL;
struct sgrp sgrp, *result;
buf = malloc(buflen);
if (!buf)
return -ENOMEM;
r = getsgnam_r(grp->gr_name, &sgrp, buf, buflen, &result);
if (r == 0) {
if (!result)
return -ESRCH;
*ret_sgrp = *result;
*ret_buffer = TAKE_PTR(buf);
return 0;
}
if (r < 0)
return -EIO; /* Weird, this should not return negative! */
if (r != ERANGE)
return -r;
if (buflen > SIZE_MAX / 2)
return -ERANGE;
buflen *= 2;
buf = mfree(buf);
}
}
int nss_group_record_by_name(
const char *name,
bool with_shadow,
GroupRecord **ret) {
_cleanup_free_ char *buf = NULL, *sbuf = NULL;
struct group grp, *result;
bool incomplete = false;
size_t buflen = 4096;
struct sgrp sgrp, *sresult = NULL;
int r;
assert(name);
assert(ret);
for (;;) {
buf = malloc(buflen);
if (!buf)
return -ENOMEM;
r = getgrnam_r(name, &grp, buf, buflen, &result);
if (r == 0) {
if (!result)
return -ESRCH;
break;
}
if (r < 0)
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getgrnam_r() returned a negative value");
if (r != ERANGE)
return -r;
if (buflen > SIZE_MAX / 2)
return -ERANGE;
buflen *= 2;
buf = mfree(buf);
}
if (with_shadow) {
r = nss_sgrp_for_group(result, &sgrp, &sbuf);
if (r < 0) {
log_debug_errno(r, "Failed to do shadow lookup for group %s, ignoring: %m", result->gr_name);
incomplete = ERRNO_IS_PRIVILEGE(r);
} else
sresult = &sgrp;
} else
incomplete = true;
r = nss_group_to_group_record(result, sresult, ret);
if (r < 0)
return r;
(*ret)->incomplete = incomplete;
return 0;
}
int nss_group_record_by_gid(
gid_t gid,
bool with_shadow,
GroupRecord **ret) {
_cleanup_free_ char *buf = NULL, *sbuf = NULL;
struct group grp, *result;
bool incomplete = false;
size_t buflen = 4096;
struct sgrp sgrp, *sresult = NULL;
int r;
assert(ret);
for (;;) {
buf = malloc(buflen);
if (!buf)
return -ENOMEM;
r = getgrgid_r(gid, &grp, buf, buflen, &result);
if (r == 0) {
if (!result)
return -ESRCH;
break;
}
if (r < 0)
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getgrgid_r() returned a negative value");
if (r != ERANGE)
return -r;
if (buflen > SIZE_MAX / 2)
return -ERANGE;
buflen *= 2;
buf = mfree(buf);
}
if (with_shadow) {
r = nss_sgrp_for_group(result, &sgrp, &sbuf);
if (r < 0) {
log_debug_errno(r, "Failed to do shadow lookup for group %s, ignoring: %m", result->gr_name);
incomplete = ERRNO_IS_PRIVILEGE(r);
} else
sresult = &sgrp;
} else
incomplete = true;
r = nss_group_to_group_record(result, sresult, ret);
if (r < 0)
return r;
(*ret)->incomplete = incomplete;
return 0;
}

View File

@ -1,15 +1,24 @@
/* SPDX-License-Identifier: LGPL-2.1+ */ /* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once #pragma once
#include <grp.h>
#include <gshadow.h>
#include <pwd.h> #include <pwd.h>
#include <shadow.h> #include <shadow.h>
#include "group-record.h"
#include "user-record.h" #include "user-record.h"
/* Synthesizes a UserRecord object from NSS data */ /* Synthesize UserRecord and GroupRecord objects from NSS data */
int nss_passwd_to_user_record(const struct passwd *pwd, const struct spwd *spwd, UserRecord **ret); int nss_passwd_to_user_record(const struct passwd *pwd, const struct spwd *spwd, UserRecord **ret);
int nss_spwd_for_passwd(const struct passwd *pwd, struct spwd *ret_spwd, char **ret_buffer); int nss_spwd_for_passwd(const struct passwd *pwd, struct spwd *ret_spwd, char **ret_buffer);
int nss_user_record_by_name(const char *name, bool with_shadow, UserRecord **ret); int nss_user_record_by_name(const char *name, bool with_shadow, UserRecord **ret);
int nss_user_record_by_uid(uid_t uid, bool with_shadow, UserRecord **ret); int nss_user_record_by_uid(uid_t uid, bool with_shadow, UserRecord **ret);
int nss_group_to_group_record(const struct group *grp, const struct sgrp *sgrp, GroupRecord **ret);
int nss_sgrp_for_group(const struct group *grp, struct sgrp *ret_sgrp, char **ret_buffer);
int nss_group_record_by_name(const char *name, bool with_shadow, GroupRecord **ret);
int nss_group_record_by_gid(gid_t gid, bool with_shadow, GroupRecord **ret);

View File

@ -2,7 +2,6 @@
#include "format-util.h" #include "format-util.h"
#include "fs-util.h" #include "fs-util.h"
#include "group-record.h"
#include "process-util.h" #include "process-util.h"
#include "rlimit-util.h" #include "rlimit-util.h"
#include "strv.h" #include "strv.h"
@ -506,3 +505,75 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) {
if (hr->service) if (hr->service)
printf(" Service: %s\n", hr->service); printf(" Service: %s\n", hr->service);
} }
void group_record_show(GroupRecord *gr, bool show_full_user_info) {
int r;
printf(" Group name: %s\n",
group_record_group_name_and_realm(gr));
printf(" Disposition: %s\n", user_disposition_to_string(group_record_disposition(gr)));
if (gr->last_change_usec != USEC_INFINITY) {
char buf[FORMAT_TIMESTAMP_MAX];
printf(" Last Change: %s\n", format_timestamp(buf, sizeof(buf), gr->last_change_usec));
}
if (gid_is_valid(gr->gid))
printf(" GID: " GID_FMT "\n", gr->gid);
if (show_full_user_info) {
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
r = membershipdb_by_group(gr->group_name, 0, &iterator);
if (r < 0) {
errno = -r;
printf(" Members: (can't acquire: %m)");
} else {
const char *prefix = " Members:";
for (;;) {
_cleanup_free_ char *user = NULL;
r = membershipdb_iterator_get(iterator, &user, NULL);
if (r == -ESRCH)
break;
if (r < 0) {
errno = -r;
printf("%s (can't iterate: %m\n", prefix);
break;
}
printf("%s %s\n", prefix, user);
prefix = " ";
}
}
} else {
const char *prefix = " Members:";
char **i;
STRV_FOREACH(i, gr->members) {
printf("%s %s\n", prefix, *i);
prefix = " ";
}
}
if (!strv_isempty(gr->administrators)) {
const char *prefix = " Admins:";
char **i;
STRV_FOREACH(i, gr->administrators) {
printf("%s %s\n", prefix, *i);
prefix = " ";
}
}
if (gr->description && !streq(gr->description, gr->group_name))
printf(" Description: %s\n", gr->description);
if (!strv_isempty(gr->hashed_password))
printf(" Passwords: %zu\n", strv_length(gr->hashed_password));
if (gr->service)
printf(" Service: %s\n", gr->service);
}

View File

@ -2,7 +2,9 @@
#pragma once #pragma once
#include "user-record.h" #include "user-record.h"
#include "group-record.h"
const char *user_record_state_color(const char *state); const char *user_record_state_color(const char *state);
void user_record_show(UserRecord *hr, bool show_full_group_info); void user_record_show(UserRecord *hr, bool show_full_group_info);
void group_record_show(GroupRecord *gr, bool show_full_user_info);

View File

@ -6,7 +6,6 @@
#include "dlfcn-util.h" #include "dlfcn-util.h"
#include "errno-util.h" #include "errno-util.h"
#include "fd-util.h" #include "fd-util.h"
#include "group-record-nss.h"
#include "missing_syscall.h" #include "missing_syscall.h"
#include "parse-util.h" #include "parse-util.h"
#include "set.h" #include "set.h"

View File

@ -9,6 +9,7 @@
#include "io-util.h" #include "io-util.h"
#include "list.h" #include "list.h"
#include "process-util.h" #include "process-util.h"
#include "selinux-util.h"
#include "set.h" #include "set.h"
#include "socket-util.h" #include "socket-util.h"
#include "string-table.h" #include "string-table.h"
@ -579,11 +580,17 @@ static int varlink_parse_message(Varlink *v) {
sz = e - begin + 1; sz = e - begin + 1;
varlink_log(v, "New incoming message: %s", begin); varlink_log(v, "New incoming message: %s", begin); /* FIXME: should we output the whole message here before validation?
* This may produce a non-printable journal entry if the message
* is invalid. We may also expose privileged information. */
r = json_parse(begin, 0, &v->current, NULL, NULL); r = json_parse(begin, 0, &v->current, NULL, NULL);
if (r < 0) if (r < 0) {
return r; /* If we encounter a parse failure flush all data. We cannot possibly recover from this,
* hence drop all buffered data now. */
v->input_buffer_index = v->input_buffer_size = v->input_buffer_unscanned = 0;
return varlink_log_errno(v, r, "Failed to parse JSON: %m");
}
v->input_buffer_size -= sz; v->input_buffer_size -= sz;
@ -2243,9 +2250,11 @@ int varlink_server_listen_address(VarlinkServer *s, const char *address, mode_t
(void) sockaddr_un_unlink(&sockaddr.un); (void) sockaddr_un_unlink(&sockaddr.un);
RUN_WITH_UMASK(~m & 0777) RUN_WITH_UMASK(~m & 0777) {
if (bind(fd, &sockaddr.sa, sockaddr_len) < 0) r = mac_selinux_bind(fd, &sockaddr.sa, sockaddr_len);
return -errno; if (r < 0)
return r;
}
if (listen(fd, SOMAXCONN) < 0) if (listen(fd, SOMAXCONN) < 0)
return -errno; return -errno;

View File

@ -3,6 +3,7 @@
#include <math.h> #include <math.h>
#include "alloc-util.h" #include "alloc-util.h"
#include "escape.h"
#include "fd-util.h" #include "fd-util.h"
#include "fileio.h" #include "fileio.h"
#include "json-internal.h" #include "json-internal.h"
@ -17,6 +18,10 @@ static void test_tokenizer(const char *data, ...) {
void *state = NULL; void *state = NULL;
va_list ap; va_list ap;
_cleanup_free_ char *cdata;
assert_se(cdata = cescape(data));
log_info("/* %s data=\"%s\" */", __func__, cdata);
va_start(ap, data); va_start(ap, data);
for (;;) { for (;;) {
@ -82,6 +87,10 @@ static void test_variant(const char *data, Test test) {
_cleanup_free_ char *s = NULL; _cleanup_free_ char *s = NULL;
int r; int r;
_cleanup_free_ char *cdata;
assert_se(cdata = cescape(data));
log_info("/* %s data=\"%s\" */", __func__, cdata);
r = json_parse(data, 0, &v, NULL, NULL); r = json_parse(data, 0, &v, NULL, NULL);
assert_se(r == 0); assert_se(r == 0);
assert_se(v); assert_se(v);
@ -140,6 +149,8 @@ static void test_1(JsonVariant *v) {
JsonVariant *p, *q; JsonVariant *p, *q;
unsigned i; unsigned i;
log_info("/* %s */", __func__);
/* 3 keys + 3 values */ /* 3 keys + 3 values */
assert_se(json_variant_elements(v) == 6); assert_se(json_variant_elements(v) == 6);
@ -173,6 +184,8 @@ static void test_1(JsonVariant *v) {
static void test_2(JsonVariant *v) { static void test_2(JsonVariant *v) {
JsonVariant *p, *q; JsonVariant *p, *q;
log_info("/* %s */", __func__);
/* 2 keys + 2 values */ /* 2 keys + 2 values */
assert_se(json_variant_elements(v) == 4); assert_se(json_variant_elements(v) == 4);
@ -216,13 +229,12 @@ static void test_2(JsonVariant *v) {
} }
static void test_zeroes(JsonVariant *v) { static void test_zeroes(JsonVariant *v) {
size_t i;
/* Make sure zero is how we expect it. */ /* Make sure zero is how we expect it. */
log_info("/* %s */", __func__);
assert_se(json_variant_elements(v) == 13); assert_se(json_variant_elements(v) == 13);
for (i = 0; i < json_variant_elements(v); i++) { for (size_t i = 0; i < json_variant_elements(v); i++) {
JsonVariant *w; JsonVariant *w;
size_t j; size_t j;
@ -255,6 +267,8 @@ static void test_zeroes(JsonVariant *v) {
} }
static void test_build(void) { static void test_build(void) {
log_info("/* %s */", __func__);
_cleanup_(json_variant_unrefp) JsonVariant *a = NULL, *b = NULL; _cleanup_(json_variant_unrefp) JsonVariant *a = NULL, *b = NULL;
_cleanup_free_ char *s = NULL, *t = NULL; _cleanup_free_ char *s = NULL, *t = NULL;
@ -355,6 +369,8 @@ static void test_source(void) {
"false, 7.5, {} ]\n" "false, 7.5, {} ]\n"
"}\n"; "}\n";
log_info("/* %s */", __func__);
_cleanup_fclose_ FILE *f = NULL; _cleanup_fclose_ FILE *f = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL; _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
@ -376,15 +392,16 @@ static void test_source(void) {
} }
static void test_depth(void) { static void test_depth(void) {
log_info("/* %s */", __func__);
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL; _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
unsigned i;
int r; int r;
v = JSON_VARIANT_STRING_CONST("start"); v = JSON_VARIANT_STRING_CONST("start");
/* Let's verify that the maximum depth checks work */ /* Let's verify that the maximum depth checks work */
for (i = 0;; i++) { for (unsigned i = 0;; i++) {
_cleanup_(json_variant_unrefp) JsonVariant *w = NULL; _cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
assert_se(i <= UINT16_MAX); assert_se(i <= UINT16_MAX);
@ -415,6 +432,8 @@ static void test_depth(void) {
} }
static void test_normalize(void) { static void test_normalize(void) {
log_info("/* %s */", __func__);
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *w = NULL; _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *w = NULL;
_cleanup_free_ char *t = NULL; _cleanup_free_ char *t = NULL;
@ -459,12 +478,13 @@ static void test_normalize(void) {
} }
static void test_bisect(void) { static void test_bisect(void) {
log_info("/* %s */", __func__);
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL; _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
char c;
/* Tests the bisection logic in json_variant_by_key() */ /* Tests the bisection logic in json_variant_by_key() */
for (c = 'z'; c >= 'a'; c--) { for (char c = 'z'; c >= 'a'; c--) {
if ((c % 3) == 0) if ((c % 3) == 0)
continue; continue;
@ -484,7 +504,7 @@ static void test_bisect(void) {
json_variant_dump(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, NULL, NULL); json_variant_dump(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, NULL, NULL);
for (c = 'a'; c <= 'z'; c++) { for (char c = 'a'; c <= 'z'; c++) {
JsonVariant *k; JsonVariant *k;
const char *z; const char *z;
@ -543,7 +563,7 @@ int main(int argc, char *argv[]) {
test_variant("{\"k\": \"v\", \"foo\": [1, 2, 3], \"bar\": {\"zap\": null}}", test_1); test_variant("{\"k\": \"v\", \"foo\": [1, 2, 3], \"bar\": {\"zap\": null}}", test_1);
test_variant("{\"mutant\": [1, null, \"1\", {\"1\": [1, \"1\"]}], \"thisisaverylongproperty\": 1.27}", test_2); test_variant("{\"mutant\": [1, null, \"1\", {\"1\": [1, \"1\"]}], \"thisisaverylongproperty\": 1.27}", test_2);
test_variant("{\"foo\" : \"\\uDBFF\\uDFFF\\\"\\uD9FF\\uDFFFFFF\\\"\\uDBFF\\uDFFF\\\"\\uD9FF\\uDFFF\\uDBFF\\uDFFFF\\uDBFF\\uDFFF\\uDBFF\\uDFFF\\uDBFF\\uDFFF\\uDBFF\\uDFFF\\\"\\uD9FF\\uDFFFFF\\\"\\uDBFF\\uDFFF\\\"\\uD9FF\\uDFFF\\uDBFF\\uDFFF\"}", NULL); test_variant("{\"foo\" : \"\\u0935\\u093f\\u0935\\u0947\\u0915\\u0916\\u094d\\u092f\\u093e\\u0924\\u093f\\u0930\\u0935\\u093f\\u092a\\u094d\\u0932\\u0935\\u093e\\u0020\\u0939\\u093e\\u0928\\u094b\\u092a\\u093e\\u092f\\u0903\\u0964\"}", NULL);
test_variant("[ 0, -0, 0.0, -0.0, 0.000, -0.000, 0e0, -0e0, 0e+0, -0e-0, 0e-0, -0e000, 0e+000 ]", test_zeroes); test_variant("[ 0, -0, 0.0, -0.0, 0.000, -0.000, 0e0, -0e0, 0e+0, -0e-0, 0e-0, -0e000, 0e+000 ]", test_zeroes);

View File

@ -18,6 +18,25 @@ static void test_utf8_is_printable(void) {
assert_se(utf8_is_printable("\t", 1)); assert_se(utf8_is_printable("\t", 1));
} }
static void test_utf8_n_is_valid(void) {
log_info("/* %s */", __func__);
assert_se( utf8_is_valid_n("ascii is valid unicode", 21));
assert_se( utf8_is_valid_n("ascii is valid unicode", 22));
assert_se(!utf8_is_valid_n("ascii is valid unicode", 23));
assert_se( utf8_is_valid_n("\342\204\242", 0));
assert_se(!utf8_is_valid_n("\342\204\242", 1));
assert_se(!utf8_is_valid_n("\342\204\242", 2));
assert_se( utf8_is_valid_n("\342\204\242", 3));
assert_se(!utf8_is_valid_n("\342\204\242", 4));
assert_se( utf8_is_valid_n("<ZZ>", 0));
assert_se( utf8_is_valid_n("<ZZ>", 1));
assert_se( utf8_is_valid_n("<ZZ>", 2));
assert_se( utf8_is_valid_n("<ZZ>", 3));
assert_se( utf8_is_valid_n("<ZZ>", 4));
assert_se(!utf8_is_valid_n("<ZZ>", 5));
}
static void test_utf8_is_valid(void) { static void test_utf8_is_valid(void) {
log_info("/* %s */", __func__); log_info("/* %s */", __func__);
@ -216,6 +235,7 @@ static void test_utf8_to_utf16(void) {
} }
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
test_utf8_n_is_valid();
test_utf8_is_valid(); test_utf8_is_valid();
test_utf8_is_printable(); test_utf8_is_printable();
test_ascii_is_valid(); test_ascii_is_valid();

View File

@ -8,7 +8,6 @@
#include "fd-util.h" #include "fd-util.h"
#include "format-table.h" #include "format-table.h"
#include "format-util.h" #include "format-util.h"
#include "group-record-show.h"
#include "main-func.h" #include "main-func.h"
#include "pager.h" #include "pager.h"
#include "parse-util.h" #include "parse-util.h"
@ -687,7 +686,8 @@ static int parse_argv(int argc, char *argv[]) {
else if (streq(optarg, "help")) { else if (streq(optarg, "help")) {
puts("classic\n" puts("classic\n"
"friendly\n" "friendly\n"
"json"); "json\n"
"table");
return 0; return 0;
} else } else
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid --output= mode: %s", optarg); return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid --output= mode: %s", optarg);

View File

@ -11,7 +11,7 @@
/* This service offers two Varlink services, both implementing io.systemd.UserDatabase: /* This service offers two Varlink services, both implementing io.systemd.UserDatabase:
* *
* io.systemd.NameServiceSwitch: this is a compatibility interface for glibc NSS: it response to * io.systemd.NameServiceSwitch: this is a compatibility interface for glibc NSS: it responds to
* name lookups by checking the classic NSS interfaces and responding that. * name lookups by checking the classic NSS interfaces and responding that.
* *
* io.systemd.Multiplexer: this multiplexes lookup requests to all Varlink services that have a * io.systemd.Multiplexer: this multiplexes lookup requests to all Varlink services that have a

View File

@ -7,7 +7,6 @@
#include "env-util.h" #include "env-util.h"
#include "fd-util.h" #include "fd-util.h"
#include "group-record-nss.h"
#include "group-record.h" #include "group-record.h"
#include "io-util.h" #include "io-util.h"
#include "main-func.h" #include "main-func.h"