mirror of
https://github.com/systemd/systemd-stable.git
synced 2025-01-27 14:03:43 +03:00
Merge pull request #14375 from poettering/userdb
New varlink API for user and group management
This commit is contained in:
commit
dd1b23a313
@ -73,6 +73,13 @@ All tools:
|
||||
appropriate path under /run. This variable is also set by the manager when
|
||||
RuntimeDirectory= is used, see systemd.exec(5).
|
||||
|
||||
* `$SYSTEMD_CRYPT_PREFIX` — if set configures the hash method prefix to use for
|
||||
UNIX crypt() when generating passwords. By default the system's "preferred
|
||||
method" is used, but this can be overridden with this environment
|
||||
variable. Takes a prefix such as `$6$` or `$y$`. (Note that this is only
|
||||
honoured on systems built with libxcrypt and is ignored on systems using
|
||||
glibc's original, internal crypt() implementation.)
|
||||
|
||||
systemctl:
|
||||
|
||||
* `$SYSTEMCTL_FORCE_BUS=1` — if set, do not connect to PID1's private D-Bus
|
||||
|
158
docs/GROUP_RECORD.md
Normal file
158
docs/GROUP_RECORD.md
Normal file
@ -0,0 +1,158 @@
|
||||
---
|
||||
title: JSON Group Records
|
||||
category: Interfaces
|
||||
layout: default
|
||||
---
|
||||
|
||||
# JSON Group Records
|
||||
|
||||
Long story short: JSON Group Records are to `struct group` what [JSON User
|
||||
Records](https://systemd.io/USER_RECORD.md) are to `struct passwd`.
|
||||
|
||||
Conceptually, much of what applies to JSON user records also applies to JSON
|
||||
group records. They also consist of seven sections, with similar properties and
|
||||
they carry some identical (or at least very similar) fields.
|
||||
|
||||
## Fields in the `regular` section
|
||||
|
||||
`groupName` → A string with the UNIX group name. Matches the `gr_name` field of
|
||||
UNIX/glibc NSS `struct group`, or the shadow structure `struct sgrp`'s
|
||||
`sg_namp` field.
|
||||
|
||||
`realm` → The "realm" the group belongs to, conceptually identical to the same
|
||||
field of user records. A string in DNS domain name syntax.
|
||||
|
||||
`disposition` → The disposition of the group, conceptually identical to the
|
||||
same field of user records. A string.
|
||||
|
||||
`service` → A string, an identifier for the service managing this group record
|
||||
(this field is typically in reverse domain name syntax.)
|
||||
|
||||
`lastChangeUSec` → An unsigned 64bit integer, a timestamp (in µs since the UNIX
|
||||
epoch 1970) of the last time the group record has been modified. (Covers only
|
||||
the `regular`, `perMachine` and `privileged` sections).
|
||||
|
||||
`gid` → An unsigned integer in the range 0…4294967295: the numeric UNIX group
|
||||
ID (GID) to use for the group. This corresponds to the `gr_gid` field of
|
||||
`struct group`.
|
||||
|
||||
`members` → An array of strings, listing user names that are members of this
|
||||
group. Note that JSON user records also contain a `memberOf` field, or in other
|
||||
words a group membership can either be denoted in the JSON user record or in
|
||||
the JSON group record, or in both. The list of memberships should be determined
|
||||
as the combination of both lists (plus optionally others). If a user is listed
|
||||
as member of a group and doesn't exist it should be ignored. This field
|
||||
corresponds to the `gr_mem` field of `struct group` and the `sg_mem` field of
|
||||
`struct sgrp`.
|
||||
|
||||
`administrators` → Similarly, an array of strings, listing user names that
|
||||
shall be considered "administrators" of this group. This field corresponds to
|
||||
the `sg_adm` field of `struct sgrp`.
|
||||
|
||||
`privileged`/`perMachine`/`binding`/`status`/`signature`/`secret` → The
|
||||
objects/arrays for the other six group record sections. These are organized the
|
||||
same way as for the JSON user records, and have the same semantics.
|
||||
|
||||
## Fields in the `privileged` section
|
||||
|
||||
The following fields are defined:
|
||||
|
||||
`hashedPassword` → An array of strings with UNIX hashed passwords; see the
|
||||
matching field for user records for details. This field corresponds to the
|
||||
`sg_passwd` field of `struct sgrp` (and `gr_passwd` of `struct group` in a
|
||||
way).
|
||||
|
||||
## Fields in the `perMachine` section
|
||||
|
||||
`matchMachineId`/`matchHostname` → Strings, match expressions similar as for
|
||||
user records, see the user record documentation for details.
|
||||
|
||||
The following fields are defined for the `perMachine` section and are defined
|
||||
equivalent to the fields of the same name in the `regular` section, and
|
||||
override those:
|
||||
|
||||
`gid`, `members`, `administrators`
|
||||
|
||||
## Fields in the `binding` section
|
||||
|
||||
The following fields are defined for the `binding` section, and are equivalent
|
||||
to the fields of the same name in the `regular` and `perMachine` sections:
|
||||
|
||||
`gid`
|
||||
|
||||
## Fields in the `status` section
|
||||
|
||||
The following fields are defined in the `status` section, and are mostly
|
||||
equivalent to the fields of the same name in the `regular` section, though with
|
||||
slightly different conceptual semantics, see the same fields in the user record
|
||||
documentation:
|
||||
|
||||
`service`
|
||||
|
||||
## Fields in the `signature` section
|
||||
|
||||
The fields in this section are defined identically to those in the matching
|
||||
section in the user record.
|
||||
|
||||
## Fields in the `secret` section
|
||||
|
||||
Currently no fields are defined in this section for group records.
|
||||
|
||||
## Mapping to `struct group` and `struct sgrp`
|
||||
|
||||
When mapping classic UNIX group records (i.e. `struct group` and `struct sgrp`)
|
||||
to JSON group records the following mappings should be applied:
|
||||
|
||||
| Structure | Field | Section | Field | Condition |
|
||||
|----------------|-------------|--------------|------------------|----------------------------|
|
||||
| `struct group` | `gr_name` | `regular` | `groupName` | |
|
||||
| `struct group` | `gr_passwd` | `privileged` | `password` | (See notes below) |
|
||||
| `struct group` | `gr_gid` | `regular` | `gid` | |
|
||||
| `struct group` | `gr_mem` | `regular` | `members` | |
|
||||
| `struct sgrp` | `sg_namp` | `regular` | `groupName` | |
|
||||
| `struct sgrp` | `sg_passwd` | `privileged` | `password` | (See notes below) |
|
||||
| `struct sgrp` | `sg_adm` | `regular` | `administrators` | |
|
||||
| `struct sgrp` | `sg_mem` | `regular` | `members` | |
|
||||
|
||||
At this time almost all Linux machines employ shadow passwords, thus the
|
||||
`gr_passwd` field in `struct group` is set to `"x"`, and the actual password
|
||||
is stored in the shadow entry `struct sgrp`'s field `sg_passwd`.
|
||||
|
||||
## Extending These Records
|
||||
|
||||
The same logic and recommendations apply as for JSON user records.
|
||||
|
||||
## Examples
|
||||
|
||||
A reasonable group record for a system group might look like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"groupName" : "systemd-resolve",
|
||||
"gid" : 193,
|
||||
"status" : {
|
||||
"6b18704270e94aa896b003b4340978f1" : {
|
||||
"service" : "io.systemd.NameServiceSwitch"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And here's a more complete one for a regular group:
|
||||
|
||||
```json
|
||||
{
|
||||
"groupName" : "grobie",
|
||||
"binding" : {
|
||||
"6b18704270e94aa896b003b4340978f1" : {
|
||||
"gid" : 60232
|
||||
}
|
||||
},
|
||||
"disposition" : "regular",
|
||||
"status" : {
|
||||
"6b18704270e94aa896b003b4340978f1" : {
|
||||
"service" : "io.systemd.Home"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
267
docs/USER_GROUP_API.md
Normal file
267
docs/USER_GROUP_API.md
Normal file
@ -0,0 +1,267 @@
|
||||
---
|
||||
title: User/Group Record Lookup API via Varlink
|
||||
category: Interfaces
|
||||
layout: default
|
||||
---
|
||||
|
||||
# User/Group Record Lookup API via Varlink
|
||||
|
||||
JSON User/Group Records (as described in the [JSON User
|
||||
Records](https://systemd.io/USER_RECORD) and [JSON Group
|
||||
Records](https://systemd.io/GROUP_RECORD) documents) that are defined on the
|
||||
local system may be queried with a [Varlink](https://varlink.org/) API. This
|
||||
API takes both the role of what
|
||||
[`getpwnam(3)`](http://man7.org/linux/man-pages/man3/getpwnam.3.html) and
|
||||
related calls are for `struct passwd`, as well as the interfaces modules
|
||||
implementing the [glibc Name Service Switch
|
||||
(NSS)](https://www.gnu.org/software/libc/manual/html_node/Name-Service-Switch.html)
|
||||
expose. Or in other words, it both allows applications to efficiently query
|
||||
user/group records from local services, and allows local subsystems to provide
|
||||
user/group records efficiently to local applications.
|
||||
|
||||
This simple API only exposes only three method calls, and requires only a small
|
||||
subset of the Varlink functionality.
|
||||
|
||||
## Why Varlink?
|
||||
|
||||
The API described in this document is based on a simple subset of the
|
||||
mechanisms described by [Varlink](https://varlink.org/). The choice of
|
||||
preferring Varlink over D-Bus and other IPCs in this context was made for three
|
||||
reasons:
|
||||
|
||||
1. User/Group record resolution should work during early boot and late shutdown
|
||||
without special handling. This is very hard to do with D-Bus, as the broker
|
||||
service for D-Bus generally runs as regular system daemon and is hence only
|
||||
available at the latest boot stage.
|
||||
|
||||
2. The JSON user/group records are native JSON data, hence picking an IPC
|
||||
system that natively operates with JSON data is natural and clean.
|
||||
|
||||
3. IPC systems such as D-Bus do not provide flow control and are thus unusable
|
||||
for streaming data. They are useful to pass around short control messages,
|
||||
but as soon as potentially many and large objects shall be transferred,
|
||||
D-Bus is not suitable, as any such streaming of messages would be considered
|
||||
flooding in D-Bus' logic, and thus possibly result in termination of
|
||||
communication. Since the APIs defined in this document need to support
|
||||
enumerating potentially large numbers of users and groups, D-Bus is simply
|
||||
not an appropriate option.
|
||||
|
||||
## Concepts
|
||||
|
||||
Each subsystem that needs to define users and groups on the local system is
|
||||
supposed to implement this API, and offer its interfaces on a Varlink
|
||||
`AF_UNIX`/`SOCK_STREAM` file system socket bound into the
|
||||
`/run/systemd/userdb/` directory. When a client wants to look up a user or
|
||||
group record, it contacts all sockets bound in this directory in parallel, and
|
||||
enqueues the same query to each. The first positive reply is then returned to
|
||||
the application, or if all fail the last seen error is returned
|
||||
instead. (Alternatively a special Varlink service is available,
|
||||
`io.systemd.Multiplexer` which acts as frontend and will do the parallel
|
||||
queries on behalf of the client, drastically simplifying client
|
||||
development. This service is not available during earliest boot and final
|
||||
shutdown phases.)
|
||||
|
||||
Unlike with glibc NSS there's no order or programmatic expression language
|
||||
defined in which queries are issued to the various services. Instead, all
|
||||
queries are always enqueued in parallel to all defined services, in order to
|
||||
make look-ups efficient, and the simple rule of "first successful lookup wins"
|
||||
is unconditionally followed for user and group look-ups (though not for
|
||||
membership lookups, see below).
|
||||
|
||||
This simple scheme only works safely as long as every service providing
|
||||
user/group records carefully makes sure not to answer with conflicting
|
||||
records. This API does not define any mechanisms for dealing with user/group
|
||||
name/ID collisions during look-up nor during record registration. It assumes
|
||||
the various subsystems that want to offer user and group records to the rest of
|
||||
the system have made sufficiently sure in advance that their definitions do not
|
||||
collide with those of other services. Clients are not expected to merge
|
||||
multiple definitions for the same user or group, and will also not be able to
|
||||
detect conflicts and suppress such conflicting records.
|
||||
|
||||
It is recommended to name the sockets in the directory in reverse domain name
|
||||
notation, but this is neither required nor enforced.
|
||||
|
||||
## Well-Known Services
|
||||
|
||||
Any subsystem that wants to provide user/group records can do so, simply by
|
||||
binding a socket in the aforementioned directory. By default two
|
||||
services are listening there, that have special relevance:
|
||||
|
||||
1. `io.systemd.NameServiceSwitch` → This service makes the classic UNIX/glibc
|
||||
NSS user/group records available as JSON User/Group records. Any such
|
||||
records are automatically converted as needed, and possibly augmented with
|
||||
information from the shadow databases.
|
||||
|
||||
2. `io.systemd.Multiplexer` → This service multiplexes client queries to all
|
||||
other running services. It's supposed to simplify client development: in
|
||||
order to look up or enumerate user/group records it's sufficient to talk to
|
||||
one service instead of all of them in parallel. Note that it is not availabe
|
||||
during earliest boot and final shutdown phases, hence for programs running
|
||||
in that context it is preferable to implement the parallel lookup
|
||||
themselves.
|
||||
|
||||
Both these services are implemented by the same daemon
|
||||
`systemd-userdbd.service`.
|
||||
|
||||
Note that these services currently implement a subset of Varlink only. For
|
||||
example, introspection is not available, and the resolver logic is not used.
|
||||
|
||||
## Other Services
|
||||
|
||||
The `systemd` project provides two other services implementing this
|
||||
interface. Specifically:
|
||||
|
||||
1. `io.systemd.DynamicUser` → This service is implemented by the service
|
||||
manager itself, and provides records for the users and groups synthesized
|
||||
via `DynamicUser=` in unit files.
|
||||
|
||||
2. `io.systemd.Home` → This service is implemented by `systemd-homed.service`
|
||||
and provides records for the users and groups defined by the home
|
||||
directories it manages.
|
||||
|
||||
Other projects are invited to implement these services too. For example it
|
||||
would make sense for LDAP/ActiveDirectory projects to implement these
|
||||
interfaces, which would provide them a way to do per-user resource management
|
||||
enforced by systemd and defined directly in LDAP directories.
|
||||
|
||||
## Compatibility with NSS
|
||||
|
||||
Two-way compatibility with classic UNIX/glibc NSS user/group records is
|
||||
provided. When using the Varlink API, lookups into databases provided only via
|
||||
NSS (and not natively via Varlink) are handled by the
|
||||
`io.systemd.NameServiceSwitch` service (see above). When using the NSS API
|
||||
(i.e. `getpwnam()` and friends) the `nss-systemd` module will automatically
|
||||
synthesize NSS records for users/groups natively defined via a Varlink
|
||||
API. Special care is taken to avoid recursion between these two compatibility
|
||||
mechanisms.
|
||||
|
||||
Subsystems that shall provide user/group records to the system may choose
|
||||
between offering them via an NSS module or via a this Varlink API, either way
|
||||
all records are accessible via both APIs, due to the bidirectional
|
||||
forwarding. It is also possible to provide the same records via both APIs
|
||||
directly, but in that case the compatibility logic must be turned off. There
|
||||
are mechanisms in place for this, please contact the systemd project for
|
||||
details, as these are currently not documented.
|
||||
|
||||
## Caching of User Records
|
||||
|
||||
This API defines no concepts for caching records. If caching is desired it
|
||||
should be implemented in the subsystems that provide the user records, not in
|
||||
the clients consuming them.
|
||||
|
||||
## Method Calls
|
||||
|
||||
```
|
||||
interface io.systemd.UserDatabase
|
||||
|
||||
method GetUserRecord(
|
||||
uid : ?int,
|
||||
userName : ?string,
|
||||
service : string
|
||||
) -> (
|
||||
record : object,
|
||||
incomplete : boolean
|
||||
)
|
||||
|
||||
method GetGroupRecord(
|
||||
gid : ?int,
|
||||
groupName : ?string,
|
||||
service : string
|
||||
) -> (
|
||||
record : object,
|
||||
incomplete : boolean
|
||||
)
|
||||
|
||||
method GetMemberships(
|
||||
userName : ?string,
|
||||
groupName : ?string,
|
||||
service : string
|
||||
) -> (
|
||||
userName : string,
|
||||
groupName : string
|
||||
)
|
||||
|
||||
error NoRecordFound()
|
||||
error BadService()
|
||||
error ServiceNotAvailable()
|
||||
error ConflictingRecordFound()
|
||||
```
|
||||
|
||||
The `GetUserRecord` method looks up or enumerates a user record. If the `uid`
|
||||
parameter is set it specifies the numeric UNIX UID to search for. If the
|
||||
`userName` parameter is set it specifies the name of the user to search
|
||||
for. Typically, only one of the two parameters are set, depending whether a
|
||||
look-up by UID or by name is desired. However, clients may also specify both
|
||||
parameters, in which case a record matching both will be returned, and if only
|
||||
one exists that matches one of the two parameters but not the other an error of
|
||||
`ConflictingRecordFound` is returned. If neither of the two parameters are set
|
||||
the whole user database is enumerated. In this case the method call needs to be
|
||||
made with `more` set, so that multiple method call replies may be generated as
|
||||
effect, each carrying one user record.
|
||||
|
||||
The `service` parameter is mandatory and should be set to the service name
|
||||
being talked to (i.e. to the same name as the `AF_UNIX` socket path, with the
|
||||
`/run/systemd/userdb/` prefix removed). This is useful to allow implementation
|
||||
of multiple services on the same socket (which is used by
|
||||
`systemd-userdbd.service`).
|
||||
|
||||
The method call returns one or more user records, depending which type of query is
|
||||
used (see above). The record is returned in the `record` field. The
|
||||
`incomplete` field indicates whether the record is complete. Services providing
|
||||
user record lookup should only pass the `privileged` section of user records to
|
||||
clients that either match the user the record is about or to sufficiently
|
||||
privileged clients, for all others the section must be removed so that no
|
||||
sensitive data is leaked this way. The `incomplete` parameter should indicate
|
||||
whether the record has been modified like this or not (i.e. it is `true` if a
|
||||
`privileged` section existed in the user record and was removed, and `false` if
|
||||
no `privileged` section existed or one existed but hasn't been removed).
|
||||
|
||||
If no user record matching the specified UID or name is known the error
|
||||
`NoRecordFound` is returned (this is also returned if neither UID nor name are
|
||||
specified, and hence enumeration requested but the subsystem currently has no
|
||||
users defined).
|
||||
|
||||
If a method call with an incorrectly set `service` field is received
|
||||
(i.e. either not set at all, or not to the service's own name) a `BadService`
|
||||
error is generated. Finally, `ServiceNotAvailable` should be returned when the
|
||||
backing subsystem is not operational for some reason and hence no information
|
||||
about existence or non-existence of a record can be returned nor any user
|
||||
record at all. (The `service` field is defined in order to allow implementation
|
||||
of daemons that provide multiple distinct user/group services over the same
|
||||
`AF_UNIX` socket: in order to correctly determine which service a client wants
|
||||
to talk to the client needs to provide the name in each request.)
|
||||
|
||||
The `GetGroupRecord` method call works analogously but for groups.
|
||||
|
||||
The `GetMemberships` method call may be used to inquire about group
|
||||
memberships. The `userName` and `groupName` arguments take what the name
|
||||
suggests. If one of the two is specified all matching memberships are returned,
|
||||
if neither is specified all known memberships of any user and any group are
|
||||
returned. The return value is a pair of user name and group name, where the
|
||||
user is a member of the group. If both arguments are specified the specified
|
||||
membership will be tested for, but no others, and the pair is returned if it is
|
||||
defined. Unless both arguments are specified the method call needs to be made
|
||||
with `more` set, so that multiple replies can be returned (since typically
|
||||
there are are multiple members per group and also multiple groups a user is
|
||||
member of). As with `GetUserRecord` and `GetGroupRecord` the `service`
|
||||
parameter needs to contain the name of the service being talked to, in order to
|
||||
allow implementation of multiple service within the same IPC socket. In case no
|
||||
matching membership is known `NoRecordFound` is returned. The other two errors
|
||||
are also generated in the same cases as for `GetUserRecord` and
|
||||
`GetGroupRecord`.
|
||||
|
||||
Unlike with `GetUserRecord` and `GetGroupRecord` the lists of memberships
|
||||
returned by services are always combined. Thus unlike the other two calls a
|
||||
membership lookup query has to wait for the last simultaneous query to complete
|
||||
before the complete list is acquired.
|
||||
|
||||
Note that only the `GetMemberships` call is authoritative about memberships of
|
||||
users in groups. i.e. it should not be considered sufficient to check the
|
||||
`memberOf` field of user records and the `members` field of group records to
|
||||
acquire the full list of memberships. The full list can only bet determined by
|
||||
`GetMemberships`, and as mentioned requires merging of these lists of all local
|
||||
services. Result of this is that it can be one service that defines a user A,
|
||||
and another service that defines a group B, and a third service that declares
|
||||
that A is a member of B.
|
||||
|
||||
And that's really all there is to it.
|
1023
docs/USER_RECORD.md
Normal file
1023
docs/USER_RECORD.md
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,7 @@
|
||||
# This file is part of systemd.
|
||||
|
||||
passwd: compat mymachines systemd
|
||||
group: compat mymachines systemd
|
||||
group: compat [SUCCESS=merge] mymachines [SUCCESS=merge] systemd
|
||||
shadow: compat
|
||||
|
||||
hosts: files mymachines resolve [!UNAVAIL=return] dns myhostname
|
||||
|
@ -18,7 +18,7 @@
|
||||
<refnamediv>
|
||||
<refname>nss-systemd</refname>
|
||||
<refname>libnss_systemd.so.2</refname>
|
||||
<refpurpose>Provide UNIX user and group name resolution for dynamic users and groups.</refpurpose>
|
||||
<refpurpose>Provide UNIX user and group name resolution for user/group lookup via Varlink</refpurpose>
|
||||
</refnamediv>
|
||||
|
||||
<refsynopsisdiv>
|
||||
@ -28,16 +28,24 @@
|
||||
<refsect1>
|
||||
<title>Description</title>
|
||||
|
||||
<para><command>nss-systemd</command> is a plug-in module for the GNU Name Service Switch (NSS) functionality of the
|
||||
GNU C Library (<command>glibc</command>), providing UNIX user and group name resolution for dynamic users and
|
||||
groups allocated through the <varname>DynamicUser=</varname> option in systemd unit files. See
|
||||
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> for details on
|
||||
this option.</para>
|
||||
<para><command>nss-systemd</command> is a plug-in module for the GNU Name Service Switch (NSS)
|
||||
functionality of the GNU C Library (<command>glibc</command>), providing UNIX user and group name
|
||||
resolution for services implementing the <ulink url="https://systemd.io/USER_GROUP_API">User/Group Record
|
||||
Lookup API via Varlink</ulink>, such as the system and service manager
|
||||
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry> (for its
|
||||
<varname>DynamicUser=</varname> feature, see
|
||||
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
|
||||
details) or
|
||||
<citerefentry><refentrytitle>systemd-homed.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
|
||||
|
||||
<para>This module also ensures that the root and nobody users and groups (i.e. the users/groups with the UIDs/GIDs
|
||||
0 and 65534) remain resolvable at all times, even if they aren't listed in <filename>/etc/passwd</filename> or
|
||||
<filename>/etc/group</filename>, or if these files are missing.</para>
|
||||
|
||||
<para>This module preferably utilizes
|
||||
<citerefentry><refentrytitle>systemd-userdbd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
|
||||
for resolving users and groups, but also works without the service running.</para>
|
||||
|
||||
<para>To activate the NSS module, add <literal>systemd</literal> to the lines starting with
|
||||
<literal>passwd:</literal> and <literal>group:</literal> in <filename>/etc/nsswitch.conf</filename>.</para>
|
||||
|
||||
@ -54,7 +62,7 @@
|
||||
|
||||
<!-- synchronize with other nss-* man pages and factory/etc/nsswitch.conf -->
|
||||
<programlisting>passwd: compat mymachines <command>systemd</command>
|
||||
group: compat mymachines <command>systemd</command>
|
||||
group: compat [SUCCESS=merge] mymachines [SUCCESS=merge] <command>systemd</command>
|
||||
shadow: compat
|
||||
|
||||
hosts: files mymachines resolve [!UNAVAIL=return] dns myhostname
|
||||
|
@ -32,6 +32,10 @@
|
||||
<citerefentry><refentrytitle>systemd-logind.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
and hence the systemd control group hierarchy.</para>
|
||||
|
||||
<para>The module also applies various resource management and runtime parameters to the new session, as
|
||||
configured in the <ulink url="https://systemd.io/USER_RECORD">JSON User Record</ulink> of the user, when
|
||||
one is defined.</para>
|
||||
|
||||
<para>On login, this module — in conjunction with <filename>systemd-logind.service</filename> — ensures the
|
||||
following:</para>
|
||||
|
||||
@ -48,7 +52,12 @@
|
||||
<listitem><para>A new systemd scope unit is created for the session. If this is the first concurrent session of
|
||||
the user, an implicit per-user slice unit below <filename>user.slice</filename> is automatically created and the
|
||||
scope placed into it. An instance of the system service <filename>user@.service</filename>, which runs the
|
||||
systemd user manager instance, is started. </para></listitem>
|
||||
systemd user manager instance, is started.</para></listitem>
|
||||
|
||||
<listitem><para>The <literal>$TZ</literal>, <literal>$EMAIL</literal> and <literal>$LANG</literal>
|
||||
environment variables are configured for the user, based on the respective data from the user's JSON
|
||||
record (if it is defined). Moreover, any environment variables explicitly configured in the user record
|
||||
are imported, and the umask, nice level, and resource limits initialized.</para></listitem>
|
||||
</orderedlist>
|
||||
|
||||
<para>On logout, this module ensures the following:</para>
|
||||
@ -172,6 +181,15 @@
|
||||
is not set if the current user is not the original user of the session.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>$TZ</varname></term>
|
||||
<term><varname>$EMAIL</varname></term>
|
||||
<term><varname>$LANG</varname></term>
|
||||
|
||||
<listitem><para>If a JSON user record is known for the user logging in these variables are
|
||||
initialized from the respective data in the record.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
</variablelist>
|
||||
|
||||
<para>The following environment variables are read by the module and may be used by the PAM service to pass
|
||||
@ -286,14 +304,23 @@ pam_set_data(handle, "systemd.runtime_max_sec", (void *)"3600", cleanup);
|
||||
<refsect1>
|
||||
<title>Example</title>
|
||||
|
||||
<para>Here's an example PAM configuration fragment that allows users sessions to be managed by
|
||||
<filename>systemd-logind.service</filename>:</para>
|
||||
|
||||
<programlisting>#%PAM-1.0
|
||||
auth required pam_unix.so
|
||||
auth required pam_nologin.so
|
||||
account required pam_unix.so
|
||||
password required pam_unix.so
|
||||
session required pam_unix.so
|
||||
session required pam_loginuid.so
|
||||
session required pam_systemd.so</programlisting>
|
||||
auth sufficient pam_unix.so
|
||||
auth required pam_deny.so
|
||||
|
||||
account required pam_nologin.so
|
||||
account sufficient pam_unix.so
|
||||
account required pam_permit.so
|
||||
|
||||
password sufficient pam_unix.so sha512 shadow try_first_pass try_authtok
|
||||
password required pam_deny.so
|
||||
|
||||
-session optional pam_loginuid.so
|
||||
-session optional pam_systemd.so
|
||||
session required pam_unix.so</programlisting>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
@ -303,6 +330,7 @@ session required pam_systemd.so</programlisting>
|
||||
<citerefentry><refentrytitle>systemd-logind.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>logind.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>loginctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>pam_systemd_home</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
<citerefentry project='man-pages'><refentrytitle>pam.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
|
||||
<citerefentry project='man-pages'><refentrytitle>pam.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
|
||||
<citerefentry project='man-pages'><refentrytitle>pam</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
|
@ -821,6 +821,7 @@ manpages = [
|
||||
['systemd-update-utmp', 'systemd-update-utmp-runlevel.service'],
|
||||
'ENABLE_UTMP'],
|
||||
['systemd-user-sessions.service', '8', ['systemd-user-sessions'], 'HAVE_PAM'],
|
||||
['systemd-userdbd.service', '8', ['systemd-userdbd'], 'ENABLE_USERDB'],
|
||||
['systemd-vconsole-setup.service',
|
||||
'8',
|
||||
['systemd-vconsole-setup'],
|
||||
@ -952,6 +953,7 @@ manpages = [
|
||||
['udev_new', '3', ['udev_ref', 'udev_unref'], ''],
|
||||
['udevadm', '8', [], ''],
|
||||
['user@.service', '5', ['user-runtime-dir@.service'], ''],
|
||||
['userdbctl', '1', [], 'ENABLE_USERDB'],
|
||||
['vconsole.conf', '5', [], 'ENABLE_VCONSOLE']
|
||||
]
|
||||
# Really, do not edit.
|
||||
|
69
man/systemd-userdbd.service.xml
Normal file
69
man/systemd-userdbd.service.xml
Normal file
@ -0,0 +1,69 @@
|
||||
<?xml version='1.0'?> <!--*-nxml-*-->
|
||||
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
|
||||
<!-- SPDX-License-Identifier: LGPL-2.1+ -->
|
||||
|
||||
<refentry id="systemd-userdbd.service" conditional='ENABLE_USERDB'>
|
||||
|
||||
<refentryinfo>
|
||||
<title>systemd-userdbd.service</title>
|
||||
<productname>systemd</productname>
|
||||
</refentryinfo>
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>systemd-userdbd.service</refentrytitle>
|
||||
<manvolnum>8</manvolnum>
|
||||
</refmeta>
|
||||
|
||||
<refnamediv>
|
||||
<refname>systemd-userdbd.service</refname>
|
||||
<refname>systemd-userdbd</refname>
|
||||
<refpurpose>JSON User/Group Record Query Multiplexer/NSS Compatibility</refpurpose>
|
||||
</refnamediv>
|
||||
|
||||
<refsynopsisdiv>
|
||||
<para><filename>systemd-userdbd.service</filename></para>
|
||||
<para><filename>/usr/lib/systemd/systemd-userdbd</filename></para>
|
||||
</refsynopsisdiv>
|
||||
|
||||
<refsect1>
|
||||
<title>Description</title>
|
||||
|
||||
<para><command>systemd-userdbd</command> is a system service that multiplexes user/group lookups to all
|
||||
local services that provide JSON user/group record definitions to the system. In addition it synthesizes
|
||||
JSON user/group records from classic UNIX/glibc NSS user/group records in order to provide full backwards
|
||||
compatibility.</para>
|
||||
|
||||
<para>Most of <command>systemd-userdbd</command>'s functionality is accessible through the
|
||||
<citerefentry><refentrytitle>userdbctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
|
||||
command.</para>
|
||||
|
||||
<para>The user and group records this service provides access to follow the <ulink
|
||||
url="https://systemd.io/USER_RECORD">JSON User Record</ulink> and <ulink
|
||||
url="https://systemd.io/GROUP_RECORD">JSON Group Record</ulink> definitions. This service implements the
|
||||
<ulink url="https://systemd.io/USER_GROUP_API">User/Group Record Lookup API via Varlink</ulink>, and
|
||||
multiplexes access other services implementing this API, too. It is thus both server and client of this
|
||||
API.</para>
|
||||
|
||||
<para>This service provides two distinct <ulink url="https://varlink.org/">Varlink</ulink> services:
|
||||
<constant>io.systemd.Multiplexer</constant> provides a single, unified API for querying JSON user and
|
||||
group records. Internally it talks to all other user/group record services running on the system in
|
||||
parallel and forwards any information discovered. This simplifies clients substantially since they need
|
||||
to talk to a single service only instead of all of them in
|
||||
parallel. <constant>io.systemd.NameSeviceSwitch</constant> provides compatibility with classic UNIX/glibc
|
||||
NSS user records, i.e. converts <type>struct passwd</type> and <type>struct group</type> records as
|
||||
acquired with APIs such as <citerefentry
|
||||
project='man-pages'><refentrytitle>getpwnam</refentrytitle><manvolnum>1</manvolnum></citerefentry> to JSON
|
||||
user/group records, thus hiding the differences between the services as much as possible.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>See Also</title>
|
||||
<para>
|
||||
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>nss-systemd</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>userdbctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>systemd-homed.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
|
||||
</para>
|
||||
</refsect1>
|
||||
</refentry>
|
258
man/userdbctl.xml
Normal file
258
man/userdbctl.xml
Normal file
@ -0,0 +1,258 @@
|
||||
<?xml version='1.0'?>
|
||||
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
|
||||
<!-- SPDX-License-Identifier: LGPL-2.1+ -->
|
||||
|
||||
<refentry id="userdbctl" conditional='ENABLE_USERDB'
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
|
||||
<refentryinfo>
|
||||
<title>userdbctl</title>
|
||||
<productname>systemd</productname>
|
||||
</refentryinfo>
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>userdbctl</refentrytitle>
|
||||
<manvolnum>1</manvolnum>
|
||||
</refmeta>
|
||||
|
||||
<refnamediv>
|
||||
<refname>userdbctl</refname>
|
||||
<refpurpose>Inspect users, groups and group memberships</refpurpose>
|
||||
</refnamediv>
|
||||
|
||||
<refsynopsisdiv>
|
||||
<cmdsynopsis>
|
||||
<command>userdbctl</command>
|
||||
<arg choice="opt" rep="repeat">OPTIONS</arg>
|
||||
<arg choice="req">COMMAND</arg>
|
||||
<arg choice="opt" rep="repeat">NAME</arg>
|
||||
</cmdsynopsis>
|
||||
</refsynopsisdiv>
|
||||
|
||||
<refsect1>
|
||||
<title>Description</title>
|
||||
|
||||
<para><command>userdbctl</command> may be used to inspect user and groups (as well as group memberships)
|
||||
of the system. This client utility inquires user/group information provided by various system services,
|
||||
both operating on JSON user/group records (as defined by the <ulink
|
||||
url="https://systemd.io/USER_RECORD">JSON User Record</ulink> and <ulink
|
||||
url="https://systemd.io/GROUP_RECORD">JSON Group Record</ulink> definitions), and classic UNIX NSS/glibc
|
||||
user and group records. This tool is primarily a client to the <ulink
|
||||
url="https://systemd.io/USER_GROUP_API">User/Group Record Lookup API via Varlink</ulink>.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Options</title>
|
||||
|
||||
<para>The following options are understood:</para>
|
||||
|
||||
<variablelist>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--output=</option><replaceable>MODE</replaceable></term>
|
||||
|
||||
<listitem><para>Choose the output mode, takes one of <literal>classic</literal>,
|
||||
<literal>friendly</literal>, <literal>table</literal>, <literal>json</literal>. If
|
||||
<literal>classic</literal>, an output very close to the format of <filename>/etc/passwd</filename> or
|
||||
<filename>/etc/group</filename> is generated. If <literal>friendly</literal> a more comprehensive and
|
||||
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
|
||||
<literal>friendly</literal> if a user/group is specified on the command line,
|
||||
<literal>table</literal> otherwise.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--service=</option><replaceable>SERVICE</replaceable><optional>:<replaceable>SERVICE…</replaceable></optional></term>
|
||||
<term><option>-s</option> <replaceable>SERVICE</replaceable>:<replaceable>SERVICE…</replaceable></term>
|
||||
|
||||
<listitem><para>Controls which services to query for users/groups. Takes a list of one or more
|
||||
service names, separated by <literal>:</literal>. See below for a list of well-known service
|
||||
names. If not specified all available services are queried at once.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--with-nss=</option><replaceable>BOOL</replaceable></term>
|
||||
|
||||
<listitem><para>Controls whether to include classic glibc/NSS user/group lookups in the output. If
|
||||
<option>--with-nss=no</option> is used any attempts to resolve or enumerate users/groups provided
|
||||
only via glibc NSS is suppressed. If <option>--with-nss=yes</option> is specified such users/groups
|
||||
are included in the output (which is the default).</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--synthesize=</option><replaceable>BOOL</replaceable></term>
|
||||
|
||||
<listitem><para>Controls whether to synthesize records for the root and nobody users/groups if they
|
||||
aren't defined otherwise. By default (or <literal>yes</literal>) such records are implicitly
|
||||
synthesized if otherwise missing since they have special significance to the OS. When
|
||||
<literal>no</literal> this synthesizing is turned off.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>-N</option></term>
|
||||
|
||||
<listitem><para>This option is short for <option>--with-nss=no</option>
|
||||
<option>--synthesize=no</option>. Use this option to show only records that are natively defined as
|
||||
JSON user or group records, with all NSS/glibc compatibility and all implicit synthesis turned
|
||||
off.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<xi:include href="standard-options.xml" xpointer="no-pager" />
|
||||
<xi:include href="standard-options.xml" xpointer="no-legend" />
|
||||
<xi:include href="standard-options.xml" xpointer="help" />
|
||||
<xi:include href="standard-options.xml" xpointer="version" />
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Commands</title>
|
||||
|
||||
<para>The following commands are understood:</para>
|
||||
|
||||
<variablelist>
|
||||
|
||||
<varlistentry>
|
||||
<term><command>user</command> <optional><replaceable>USER</replaceable>…</optional></term>
|
||||
|
||||
<listitem><para>List all known users records or show details of one or more specified user
|
||||
records. Use <option>--output=</option> to tweak output mode.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><command>group</command> <optional><replaceable>GROUP</replaceable>…</optional></term>
|
||||
|
||||
<listitem><para>List all known group records or show details of one or more specified group
|
||||
records. Use <option>--output=</option> to tweak output mode.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><command>users-in-group</command> <optional><replaceable>GROUP</replaceable>…</optional></term>
|
||||
|
||||
<listitem><para>List users that are members of the specified groups. If no groups are specified list
|
||||
all user/group memberships defined. Use <option>--output=</option> to tweak output
|
||||
mode.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><command>groups-of-user</command> <optional><replaceable>USER</replaceable>…</optional></term>
|
||||
|
||||
<listitem><para>List groups that the specified users are members of. If no users are specified list
|
||||
all user/group memberships defined (in this case <command>groups-of-user</command> and
|
||||
<command>users-in-group</command> are equivalent). Use <option>--output=</option> to tweak output
|
||||
mode.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><command>services</command></term>
|
||||
|
||||
<listitem><para>List all services currently providing user/group definitions to the system. See below
|
||||
for a list of well-known services providing user information.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><command>ssh-authorized-keys</command></term>
|
||||
|
||||
<listitem><para>This operation is not a public, user-facing interface. It is used to allow the SSH daemon to pick
|
||||
up authorized keys from user records, see below.</para></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Well-Known Services</title>
|
||||
|
||||
<para>The <command>userdbctl services</command> command will list all currently running services that
|
||||
provide user or group definitions to the system. The following are well-known services are shown among
|
||||
this list.</para>
|
||||
|
||||
<variablelist>
|
||||
|
||||
<varlistentry>
|
||||
<term><constant>io.systemd.DynamicUser</constant></term>
|
||||
|
||||
<listitem><para>This service is provided by the system service manager itself (i.e. PID 1) and
|
||||
makes all users (and their groups) synthesized through the <varname>DynamicUser=</varname> setting in
|
||||
service unit files available to the system (see
|
||||
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
|
||||
details about this setting).</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><constant>io.systemd.Home</constant></term>
|
||||
|
||||
<listitem><para>This service is provided by
|
||||
<citerefentry><refentrytitle>systemd-homed.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
|
||||
and makes all users (and their groups) belonging to home directories managed by that service
|
||||
available to the system.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><constant>io.systemd.Multiplexer</constant></term>
|
||||
|
||||
<listitem><para>This service is provided by
|
||||
<citerefentry><refentrytitle>systemd-userdbd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
|
||||
and multiplexes user/group look-ups to all other running lookup services. This is the primary entry point
|
||||
for user/group record clients, as it simplifies client side implementation substantially since they
|
||||
can ask a single service for lookups instead of asking all running services in parallel.
|
||||
<command>userdbctl</command> uses this service preferably, too, unless <option>--with-nss=</option>
|
||||
or <option>--service=</option> are used, in which case finer control over the services to talk to is
|
||||
required.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><constant>io.systemd.NameSeviceSwitch</constant></term>
|
||||
|
||||
<listitem><para>This service is (also) provided by
|
||||
<citerefentry><refentrytitle>systemd-userdbd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
|
||||
and converts classic NSS/glibc user and group records to JSON user/group records, providing full
|
||||
backwards compatibility. Use <option>--with-nss=no</option> to disable this compatibility, see
|
||||
above. Note that compatibility is actually provided in both directions:
|
||||
<citerefentry><refentrytitle>nss-systemd</refentrytitle><manvolnum>8</manvolnum></citerefentry> will
|
||||
automatically synthesize classic NSS/glibc user/group records from all JSON user/group records
|
||||
provided to the system, thus using both APIs is mostly equivalent and provides access to the same
|
||||
data, however the NSS/glibc APIs necessarily expose a more reduced set of fields
|
||||
only.</para></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
|
||||
<para>Note that <command>userdbctl</command> has internal support for NSS-based lookups too. This means
|
||||
that if neither <constant>io.systemd.Multiplexer</constant> nor
|
||||
<constant>io.systemd.NameSeviceSwitch</constant> are running look-ups into the the basic user/group
|
||||
databases will still work.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Integration with SSH</title>
|
||||
|
||||
<para>The <command>userdbctl</command> tool may be used to make the list of SSH authorized keys possibly
|
||||
contained in a user record available to the SSH daemon for authentication. For that configure the
|
||||
following in <citerefentry
|
||||
project='openssh'><refentrytitle>sshd_config</refentrytitle><manvolnum>5</manvolnum></citerefentry>:</para>
|
||||
|
||||
<programlisting>…
|
||||
AuthorizedKeysCommand /usr/bin/userdbctl ssh-authorized-keys %u
|
||||
AuthorizedKeysCommandUser root
|
||||
…</programlisting>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Exit status</title>
|
||||
|
||||
<para>On success, 0 is returned, a non-zero failure code otherwise.</para>
|
||||
</refsect1>
|
||||
|
||||
<xi:include href="less-variables.xml" />
|
||||
|
||||
<refsect1>
|
||||
<title>See Also</title>
|
||||
<para>
|
||||
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>systemd-userdbd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>systemd-homed.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>nss-systemd</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
<citerefentry project='man-pages'><refentrytitle>getent</refentrytitle><manvolnum>1</manvolnum></citerefentry>
|
||||
</para>
|
||||
</refsect1>
|
||||
|
||||
</refentry>
|
42
meson.build
42
meson.build
@ -243,6 +243,7 @@ conf.set_quoted('SYSTEMD_EXPORT_PATH', join_paths(rootlib
|
||||
conf.set_quoted('VENDOR_KEYRING_PATH', join_paths(rootlibexecdir, 'import-pubring.gpg'))
|
||||
conf.set_quoted('USER_KEYRING_PATH', join_paths(pkgsysconfdir, 'import-pubring.gpg'))
|
||||
conf.set_quoted('DOCUMENT_ROOT', join_paths(pkgdatadir, 'gatewayd'))
|
||||
conf.set_quoted('SYSTEMD_USERWORK_PATH', join_paths(rootlibexecdir, 'systemd-userwork'))
|
||||
conf.set10('MEMORY_ACCOUNTING_DEFAULT', memory_accounting_default)
|
||||
conf.set_quoted('MEMORY_ACCOUNTING_DEFAULT_YES_NO', memory_accounting_default ? 'yes' : 'no')
|
||||
conf.set('STATUS_UNIT_FORMAT_DEFAULT', 'STATUS_UNIT_FORMAT_' + status_unit_format_default.to_upper())
|
||||
@ -1322,6 +1323,7 @@ foreach term : ['utmp',
|
||||
'localed',
|
||||
'machined',
|
||||
'portabled',
|
||||
'userdb',
|
||||
'networkd',
|
||||
'timedated',
|
||||
'timesyncd',
|
||||
@ -1538,6 +1540,7 @@ subdir('src/kernel-install')
|
||||
subdir('src/locale')
|
||||
subdir('src/machine')
|
||||
subdir('src/portable')
|
||||
subdir('src/userdb')
|
||||
subdir('src/nspawn')
|
||||
subdir('src/resolve')
|
||||
subdir('src/timedate')
|
||||
@ -1564,7 +1567,7 @@ test_dlopen = executable(
|
||||
build_by_default : want_tests != 'false')
|
||||
|
||||
foreach tuple : [['myhostname', 'ENABLE_NSS_MYHOSTNAME'],
|
||||
['systemd', 'ENABLE_NSS_SYSTEMD'],
|
||||
['systemd', 'ENABLE_NSS_SYSTEMD', 'src/nss-systemd/userdb-glue.c src/nss-systemd/userdb-glue.h'],
|
||||
['mymachines', 'ENABLE_NSS_MYMACHINES'],
|
||||
['resolve', 'ENABLE_NSS_RESOLVE']]
|
||||
|
||||
@ -1575,9 +1578,14 @@ foreach tuple : [['myhostname', 'ENABLE_NSS_MYHOSTNAME'],
|
||||
sym = 'src/nss-@0@/nss-@0@.sym'.format(module)
|
||||
version_script_arg = join_paths(project_source_root, sym)
|
||||
|
||||
sources = ['src/nss-@0@/nss-@0@.c'.format(module)]
|
||||
if tuple.length() > 2
|
||||
sources += tuple[2].split()
|
||||
endif
|
||||
|
||||
nss = shared_library(
|
||||
'nss_' + module,
|
||||
'src/nss-@0@/nss-@0@.c'.format(module),
|
||||
sources,
|
||||
disable_mempool_c,
|
||||
version : '2',
|
||||
include_directories : includes,
|
||||
@ -1974,6 +1982,35 @@ if conf.get('ENABLE_PORTABLED') == 1
|
||||
public_programs += exe
|
||||
endif
|
||||
|
||||
if conf.get('ENABLE_USERDB') == 1
|
||||
executable('systemd-userwork',
|
||||
systemd_userwork_sources,
|
||||
include_directories : includes,
|
||||
link_with : [libshared],
|
||||
dependencies : [threads],
|
||||
install_rpath : rootlibexecdir,
|
||||
install : true,
|
||||
install_dir : rootlibexecdir)
|
||||
|
||||
executable('systemd-userdbd',
|
||||
systemd_userdbd_sources,
|
||||
include_directories : includes,
|
||||
link_with : [libshared],
|
||||
dependencies : [threads],
|
||||
install_rpath : rootlibexecdir,
|
||||
install : true,
|
||||
install_dir : rootlibexecdir)
|
||||
|
||||
executable('userdbctl',
|
||||
userdbctl_sources,
|
||||
include_directories : includes,
|
||||
link_with : [libshared],
|
||||
dependencies : [threads],
|
||||
install_rpath : rootlibexecdir,
|
||||
install : true,
|
||||
install_dir : rootbindir)
|
||||
endif
|
||||
|
||||
foreach alias : ['halt', 'poweroff', 'reboot', 'runlevel', 'shutdown', 'telinit']
|
||||
meson.add_install_script(meson_make_symlink,
|
||||
join_paths(rootbindir, 'systemctl'),
|
||||
@ -3252,6 +3289,7 @@ foreach tuple : [
|
||||
['logind'],
|
||||
['machined'],
|
||||
['portabled'],
|
||||
['userdb'],
|
||||
['importd'],
|
||||
['hostnamed'],
|
||||
['timedated'],
|
||||
|
@ -94,6 +94,8 @@ option('machined', type : 'boolean',
|
||||
description : 'install the systemd-machined stack')
|
||||
option('portabled', type : 'boolean',
|
||||
description : 'install the systemd-portabled stack')
|
||||
option('userdb', type : 'boolean',
|
||||
description : 'install the systemd-userdbd stack')
|
||||
option('networkd', type : 'boolean',
|
||||
description : 'install the systemd-networkd stack')
|
||||
option('timedated', type : 'boolean',
|
||||
|
@ -139,6 +139,38 @@ enum nss_status _nss_##module##_getgrgid_r( \
|
||||
char *buffer, size_t buflen, \
|
||||
int *errnop) _public_
|
||||
|
||||
#define NSS_PWENT_PROTOTYPES(module) \
|
||||
enum nss_status _nss_##module##_endpwent( \
|
||||
void) _public_; \
|
||||
enum nss_status _nss_##module##_setpwent( \
|
||||
int stayopen) _public_; \
|
||||
enum nss_status _nss_##module##_getpwent_r( \
|
||||
struct passwd *result, \
|
||||
char *buffer, \
|
||||
size_t buflen, \
|
||||
int *errnop) _public_;
|
||||
|
||||
#define NSS_GRENT_PROTOTYPES(module) \
|
||||
enum nss_status _nss_##module##_endgrent( \
|
||||
void) _public_; \
|
||||
enum nss_status _nss_##module##_setgrent( \
|
||||
int stayopen) _public_; \
|
||||
enum nss_status _nss_##module##_getgrent_r( \
|
||||
struct group *result, \
|
||||
char *buffer, \
|
||||
size_t buflen, \
|
||||
int *errnop) _public_;
|
||||
|
||||
#define NSS_INITGROUPS_PROTOTYPE(module) \
|
||||
enum nss_status _nss_##module##_initgroups_dyn( \
|
||||
const char *user, \
|
||||
gid_t group, \
|
||||
long int *start, \
|
||||
long int *size, \
|
||||
gid_t **groupsp, \
|
||||
long int limit, \
|
||||
int *errnop) _public_;
|
||||
|
||||
typedef enum nss_status (*_nss_gethostbyname4_r_t)(
|
||||
const char *name,
|
||||
struct gaih_addrtuple **pat,
|
||||
|
@ -945,40 +945,3 @@ int fgetsgent_sane(FILE *stream, struct sgrp **sg) {
|
||||
return !!s;
|
||||
}
|
||||
#endif
|
||||
|
||||
int make_salt(char **ret) {
|
||||
static const char table[] =
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"0123456789"
|
||||
"./";
|
||||
|
||||
uint8_t raw[16];
|
||||
char *salt, *j;
|
||||
size_t i;
|
||||
int r;
|
||||
|
||||
/* This is a bit like crypt_gensalt_ra(), but doesn't require libcrypt, and doesn't do anything but
|
||||
* SHA512, i.e. is legacy-free and minimizes our deps. */
|
||||
|
||||
assert_cc(sizeof(table) == 64U + 1U);
|
||||
|
||||
/* Insist on the best randomness by setting RANDOM_BLOCK, this is about keeping passwords secret after all. */
|
||||
r = genuine_random_bytes(raw, sizeof(raw), RANDOM_BLOCK);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
salt = new(char, 3+sizeof(raw)+1+1);
|
||||
if (!salt)
|
||||
return -ENOMEM;
|
||||
|
||||
/* We only bother with SHA512 hashed passwords, the rest is legacy, and we don't do legacy. */
|
||||
j = stpcpy(salt, "$6$");
|
||||
for (i = 0; i < sizeof(raw); i++)
|
||||
j[i] = table[raw[i] & 63];
|
||||
j[i++] = '$';
|
||||
j[i] = 0;
|
||||
|
||||
*ret = salt;
|
||||
return 0;
|
||||
}
|
||||
|
@ -137,6 +137,4 @@ int fgetsgent_sane(FILE *stream, struct sgrp **sg);
|
||||
int putsgent_sane(const struct sgrp *sg, FILE *stream);
|
||||
#endif
|
||||
|
||||
int make_salt(char **ret);
|
||||
|
||||
bool is_nologin_shell(const char *shell);
|
||||
|
310
src/core/core-varlink.c
Normal file
310
src/core/core-varlink.c
Normal file
@ -0,0 +1,310 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
|
||||
#include "core-varlink.h"
|
||||
#include "mkdir.h"
|
||||
#include "user-util.h"
|
||||
#include "varlink.h"
|
||||
|
||||
typedef struct LookupParameters {
|
||||
const char *user_name;
|
||||
const char *group_name;
|
||||
union {
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
};
|
||||
const char *service;
|
||||
} LookupParameters;
|
||||
|
||||
static int build_user_json(const char *user_name, uid_t uid, JsonVariant **ret) {
|
||||
assert(user_name);
|
||||
assert(uid_is_valid(uid));
|
||||
assert(ret);
|
||||
|
||||
return json_build(ret, JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR("record", JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(user_name)),
|
||||
JSON_BUILD_PAIR("uid", JSON_BUILD_UNSIGNED(uid)),
|
||||
JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(uid)),
|
||||
JSON_BUILD_PAIR("realName", JSON_BUILD_STRING("Dynamic User")),
|
||||
JSON_BUILD_PAIR("homeDirectory", JSON_BUILD_STRING("/")),
|
||||
JSON_BUILD_PAIR("shell", JSON_BUILD_STRING(NOLOGIN)),
|
||||
JSON_BUILD_PAIR("locked", JSON_BUILD_BOOLEAN(true)),
|
||||
JSON_BUILD_PAIR("service", JSON_BUILD_STRING("io.systemd.DynamicUser")),
|
||||
JSON_BUILD_PAIR("disposition", JSON_BUILD_STRING("dynamic"))))));
|
||||
}
|
||||
|
||||
static bool user_match_lookup_parameters(LookupParameters *p, const char *name, uid_t uid) {
|
||||
assert(p);
|
||||
|
||||
if (p->user_name && !streq(name, p->user_name))
|
||||
return false;
|
||||
|
||||
if (uid_is_valid(p->uid) && uid != p->uid)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int vl_method_get_user_record(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
|
||||
|
||||
static const JsonDispatch dispatch_table[] = {
|
||||
{ "uid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(LookupParameters, uid), 0 },
|
||||
{ "userName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, user_name), JSON_SAFE },
|
||||
{ "service", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, service), 0 },
|
||||
{}
|
||||
};
|
||||
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
|
||||
LookupParameters p = {
|
||||
.uid = UID_INVALID,
|
||||
};
|
||||
_cleanup_free_ char *found_name = NULL;
|
||||
uid_t found_uid = UID_INVALID, uid;
|
||||
Manager *m = userdata;
|
||||
const char *un;
|
||||
int r;
|
||||
|
||||
assert(parameters);
|
||||
assert(m);
|
||||
|
||||
r = json_dispatch(parameters, dispatch_table, NULL, 0, &p);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!streq_ptr(p.service, "io.systemd.DynamicUser"))
|
||||
return varlink_error(link, "io.systemd.UserDatabase.BadService", NULL);
|
||||
|
||||
if (uid_is_valid(p.uid))
|
||||
r = dynamic_user_lookup_uid(m, p.uid, &found_name);
|
||||
else if (p.user_name)
|
||||
r = dynamic_user_lookup_name(m, p.user_name, &found_uid);
|
||||
else {
|
||||
Iterator i;
|
||||
DynamicUser *d;
|
||||
|
||||
HASHMAP_FOREACH(d, m->dynamic_users, i) {
|
||||
r = dynamic_user_current(d, &uid);
|
||||
if (r == -EAGAIN) /* not realized yet? */
|
||||
continue;
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!user_match_lookup_parameters(&p, d->name, uid))
|
||||
continue;
|
||||
|
||||
if (v) {
|
||||
r = varlink_notify(link, v);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
v = json_variant_unref(v);
|
||||
}
|
||||
|
||||
r = build_user_json(d->name, uid, &v);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (!v)
|
||||
return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
|
||||
|
||||
return varlink_reply(link, v);
|
||||
}
|
||||
if (r == -ESRCH)
|
||||
return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
uid = uid_is_valid(found_uid) ? found_uid : p.uid;
|
||||
un = found_name ?: p.user_name;
|
||||
|
||||
if (!user_match_lookup_parameters(&p, un, uid))
|
||||
return varlink_error(link, "io.systemd.UserDatabase.ConflictingRecordFound", NULL);
|
||||
|
||||
r = build_user_json(un, uid, &v);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return varlink_reply(link, v);
|
||||
}
|
||||
|
||||
static int build_group_json(const char *group_name, gid_t gid, JsonVariant **ret) {
|
||||
assert(group_name);
|
||||
assert(gid_is_valid(gid));
|
||||
assert(ret);
|
||||
|
||||
return json_build(ret, JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR("record", JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(group_name)),
|
||||
JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(gid)),
|
||||
JSON_BUILD_PAIR("service", JSON_BUILD_STRING("io.systemd.DynamicUser")),
|
||||
JSON_BUILD_PAIR("disposition", JSON_BUILD_STRING("dynamic"))))));
|
||||
}
|
||||
|
||||
static bool group_match_lookup_parameters(LookupParameters *p, const char *name, gid_t gid) {
|
||||
assert(p);
|
||||
|
||||
if (p->group_name && !streq(name, p->group_name))
|
||||
return false;
|
||||
|
||||
if (gid_is_valid(p->gid) && gid != p->gid)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int vl_method_get_group_record(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
|
||||
|
||||
static const JsonDispatch dispatch_table[] = {
|
||||
{ "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(LookupParameters, gid), 0 },
|
||||
{ "groupName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, group_name), JSON_SAFE },
|
||||
{ "service", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, service), 0 },
|
||||
{}
|
||||
};
|
||||
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
|
||||
LookupParameters p = {
|
||||
.gid = GID_INVALID,
|
||||
};
|
||||
_cleanup_free_ char *found_name = NULL;
|
||||
uid_t found_gid = GID_INVALID, gid;
|
||||
Manager *m = userdata;
|
||||
const char *gn;
|
||||
int r;
|
||||
|
||||
assert(parameters);
|
||||
assert(m);
|
||||
|
||||
r = json_dispatch(parameters, dispatch_table, NULL, 0, &p);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!streq_ptr(p.service, "io.systemd.DynamicUser"))
|
||||
return varlink_error(link, "io.systemd.UserDatabase.BadService", NULL);
|
||||
|
||||
if (gid_is_valid(p.gid))
|
||||
r = dynamic_user_lookup_uid(m, (uid_t) p.gid, &found_name);
|
||||
else if (p.group_name)
|
||||
r = dynamic_user_lookup_name(m, p.group_name, (uid_t*) &found_gid);
|
||||
else {
|
||||
DynamicUser *d;
|
||||
Iterator i;
|
||||
|
||||
HASHMAP_FOREACH(d, m->dynamic_users, i) {
|
||||
uid_t uid;
|
||||
|
||||
r = dynamic_user_current(d, &uid);
|
||||
if (r == -EAGAIN)
|
||||
continue;
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!group_match_lookup_parameters(&p, d->name, (gid_t) uid))
|
||||
continue;
|
||||
|
||||
if (v) {
|
||||
r = varlink_notify(link, v);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
v = json_variant_unref(v);
|
||||
}
|
||||
|
||||
r = build_group_json(d->name, (gid_t) uid, &v);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (!v)
|
||||
return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
|
||||
|
||||
return varlink_reply(link, v);
|
||||
}
|
||||
if (r == -ESRCH)
|
||||
return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
gid = gid_is_valid(found_gid) ? found_gid : p.gid;
|
||||
gn = found_name ?: p.group_name;
|
||||
|
||||
if (!group_match_lookup_parameters(&p, gn, gid))
|
||||
return varlink_error(link, "io.systemd.UserDatabase.ConflictingRecordFound", NULL);
|
||||
|
||||
r = build_group_json(gn, gid, &v);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return varlink_reply(link, v);
|
||||
}
|
||||
|
||||
static int vl_method_get_memberships(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
|
||||
|
||||
static const JsonDispatch dispatch_table[] = {
|
||||
{ "userName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, user_name), JSON_SAFE },
|
||||
{ "groupName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, group_name), JSON_SAFE },
|
||||
{ "service", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, service), 0 },
|
||||
{}
|
||||
};
|
||||
|
||||
LookupParameters p = {};
|
||||
int r;
|
||||
|
||||
assert(parameters);
|
||||
|
||||
r = json_dispatch(parameters, dispatch_table, NULL, 0, &p);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!streq_ptr(p.service, "io.systemd.DynamicUser"))
|
||||
return varlink_error(link, "io.systemd.UserDatabase.BadService", NULL);
|
||||
|
||||
/* We don't support auxiliary groups with dynamic users. */
|
||||
return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
|
||||
}
|
||||
|
||||
int manager_varlink_init(Manager *m) {
|
||||
_cleanup_(varlink_server_unrefp) VarlinkServer *s = NULL;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
|
||||
if (m->varlink_server)
|
||||
return 0;
|
||||
|
||||
if (!MANAGER_IS_SYSTEM(m))
|
||||
return 0;
|
||||
|
||||
r = varlink_server_new(&s, VARLINK_SERVER_ACCOUNT_UID);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to allocate varlink server object: %m");
|
||||
|
||||
varlink_server_set_userdata(s, m);
|
||||
|
||||
r = varlink_server_bind_method_many(
|
||||
s,
|
||||
"io.systemd.UserDatabase.GetUserRecord", vl_method_get_user_record,
|
||||
"io.systemd.UserDatabase.GetGroupRecord", vl_method_get_group_record,
|
||||
"io.systemd.UserDatabase.GetMemberships", vl_method_get_memberships);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to register varlink methods: %m");
|
||||
|
||||
(void) mkdir_p("/run/systemd/userdb", 0755);
|
||||
|
||||
r = varlink_server_listen_address(s, "/run/systemd/userdb/io.systemd.DynamicUser", 0666);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to bind to varlink socket: %m");
|
||||
|
||||
r = varlink_server_attach_event(s, m->event, SD_EVENT_PRIORITY_NORMAL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to attach varlink connection to event loop: %m");
|
||||
|
||||
m->varlink_server = TAKE_PTR(s);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void manager_varlink_done(Manager *m) {
|
||||
assert(m);
|
||||
|
||||
m->varlink_server = varlink_server_unref(m->varlink_server);
|
||||
}
|
7
src/core/core-varlink.h
Normal file
7
src/core/core-varlink.h
Normal file
@ -0,0 +1,7 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#include "manager.h"
|
||||
|
||||
int manager_varlink_init(Manager *m);
|
||||
void manager_varlink_done(Manager *m);
|
@ -529,7 +529,6 @@ int dynamic_user_current(DynamicUser *d, uid_t *ret) {
|
||||
int r;
|
||||
|
||||
assert(d);
|
||||
assert(ret);
|
||||
|
||||
/* Get the currently assigned UID for the user, if there's any. This simply pops the data from the storage socket, and pushes it back in right-away. */
|
||||
|
||||
@ -545,7 +544,9 @@ int dynamic_user_current(DynamicUser *d, uid_t *ret) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
*ret = uid;
|
||||
if (ret)
|
||||
*ret = uid;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -732,7 +733,6 @@ int dynamic_user_lookup_name(Manager *m, const char *name, uid_t *ret) {
|
||||
|
||||
assert(m);
|
||||
assert(name);
|
||||
assert(ret);
|
||||
|
||||
/* A friendly call for translating a dynamic user's name into its UID */
|
||||
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include "bus-util.h"
|
||||
#include "clean-ipc.h"
|
||||
#include "clock-util.h"
|
||||
#include "core-varlink.h"
|
||||
#include "dbus-job.h"
|
||||
#include "dbus-manager.h"
|
||||
#include "dbus-unit.h"
|
||||
@ -44,8 +45,8 @@
|
||||
#include "fileio.h"
|
||||
#include "fs-util.h"
|
||||
#include "hashmap.h"
|
||||
#include "io-util.h"
|
||||
#include "install.h"
|
||||
#include "io-util.h"
|
||||
#include "label.h"
|
||||
#include "locale-setup.h"
|
||||
#include "log.h"
|
||||
@ -1346,6 +1347,7 @@ Manager* manager_free(Manager *m) {
|
||||
lookup_paths_flush_generator(&m->lookup_paths);
|
||||
|
||||
bus_done(m);
|
||||
manager_varlink_done(m);
|
||||
|
||||
exec_runtime_vacuum(m);
|
||||
hashmap_free(m->exec_runtime_by_id);
|
||||
@ -1703,6 +1705,10 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds) {
|
||||
log_warning_errno(r, "Failed to deserialized tracked clients, ignoring: %m");
|
||||
m->deserialized_subscribed = strv_free(m->deserialized_subscribed);
|
||||
|
||||
r = manager_varlink_init(m);
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to set up Varlink server, ignoring: %m");
|
||||
|
||||
/* Third, fire things up! */
|
||||
manager_coldplug(m);
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "list.h"
|
||||
#include "prioq.h"
|
||||
#include "ratelimit.h"
|
||||
#include "varlink.h"
|
||||
|
||||
struct libmnt_monitor;
|
||||
typedef struct Unit Unit;
|
||||
@ -422,6 +423,8 @@ struct Manager {
|
||||
unsigned notifygen;
|
||||
|
||||
bool honor_device_enumeration;
|
||||
|
||||
VarlinkServer *varlink_server;
|
||||
};
|
||||
|
||||
static inline usec_t manager_default_timeout_abort_usec(Manager *m) {
|
||||
|
@ -22,6 +22,8 @@ libcore_sources = '''
|
||||
bpf-firewall.h
|
||||
cgroup.c
|
||||
cgroup.h
|
||||
core-varlink.c
|
||||
core-varlink.h
|
||||
dbus-automount.c
|
||||
dbus-automount.h
|
||||
dbus-cgroup.c
|
||||
|
@ -4,19 +4,6 @@
|
||||
#include <getopt.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#if HAVE_CRYPT_H
|
||||
/* libxcrypt is a replacement for glibc's libcrypt, and libcrypt might be
|
||||
* removed from glibc at some point. As part of the removal, defines for
|
||||
* crypt(3) are dropped from unistd.h, and we must include crypt.h instead.
|
||||
*
|
||||
* Newer versions of glibc (v2.0+) already ship crypt.h with a definition
|
||||
* of crypt(3) as well, so we simply include it if it is present. MariaDB,
|
||||
* MySQL, PostgreSQL, Perl and some other wide-spread packages do it the
|
||||
* same way since ages without any problems.
|
||||
*/
|
||||
# include <crypt.h>
|
||||
#endif
|
||||
|
||||
#include "sd-id128.h"
|
||||
|
||||
#include "alloc-util.h"
|
||||
@ -28,6 +15,7 @@
|
||||
#include "fs-util.h"
|
||||
#include "hostname-util.h"
|
||||
#include "kbd-util.h"
|
||||
#include "libcrypt-util.h"
|
||||
#include "locale-util.h"
|
||||
#include "main-func.h"
|
||||
#include "memory-util.h"
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "terminal-util.h"
|
||||
#include "udev-util.h"
|
||||
#include "user-util.h"
|
||||
#include "userdb.h"
|
||||
|
||||
void manager_reset_config(Manager *m) {
|
||||
assert(m);
|
||||
@ -137,21 +138,18 @@ int manager_add_session(Manager *m, const char *id, Session **ret_session) {
|
||||
|
||||
int manager_add_user(
|
||||
Manager *m,
|
||||
uid_t uid,
|
||||
gid_t gid,
|
||||
const char *name,
|
||||
const char *home,
|
||||
UserRecord *ur,
|
||||
User **ret_user) {
|
||||
|
||||
User *u;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
assert(name);
|
||||
assert(ur);
|
||||
|
||||
u = hashmap_get(m->users, UID_TO_PTR(uid));
|
||||
u = hashmap_get(m->users, UID_TO_PTR(ur->uid));
|
||||
if (!u) {
|
||||
r = user_new(&u, m, uid, gid, name, home);
|
||||
r = user_new(&u, m, ur);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
@ -167,32 +165,35 @@ int manager_add_user_by_name(
|
||||
const char *name,
|
||||
User **ret_user) {
|
||||
|
||||
const char *home = NULL;
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
_cleanup_(user_record_unrefp) UserRecord *ur = NULL;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
assert(name);
|
||||
|
||||
r = get_user_creds(&name, &uid, &gid, &home, NULL, 0);
|
||||
r = userdb_by_name(name, 0, &ur);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return manager_add_user(m, uid, gid, name, home, ret_user);
|
||||
return manager_add_user(m, ur, ret_user);
|
||||
}
|
||||
|
||||
int manager_add_user_by_uid(Manager *m, uid_t uid, User **ret_user) {
|
||||
struct passwd *p;
|
||||
int manager_add_user_by_uid(
|
||||
Manager *m,
|
||||
uid_t uid,
|
||||
User **ret_user) {
|
||||
|
||||
_cleanup_(user_record_unrefp) UserRecord *ur = NULL;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
assert(uid_is_valid(uid));
|
||||
|
||||
errno = 0;
|
||||
p = getpwuid(uid);
|
||||
if (!p)
|
||||
return errno_or_else(ENOENT);
|
||||
r = userdb_by_uid(uid, 0, &ur);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return manager_add_user(m, uid, p->pw_gid, p->pw_name, p->pw_dir, ret_user);
|
||||
return manager_add_user(m, ur, ret_user);
|
||||
}
|
||||
|
||||
int manager_add_inhibitor(Manager *m, const char* id, Inhibitor **ret) {
|
||||
|
@ -536,8 +536,8 @@ static int method_list_sessions(sd_bus_message *message, void *userdata, sd_bus_
|
||||
|
||||
r = sd_bus_message_append(reply, "(susso)",
|
||||
session->id,
|
||||
(uint32_t) session->user->uid,
|
||||
session->user->name,
|
||||
(uint32_t) session->user->user_record->uid,
|
||||
session->user->user_record->user_name,
|
||||
session->seat ? session->seat->id : "",
|
||||
p);
|
||||
if (r < 0)
|
||||
@ -577,8 +577,8 @@ static int method_list_users(sd_bus_message *message, void *userdata, sd_bus_err
|
||||
return -ENOMEM;
|
||||
|
||||
r = sd_bus_message_append(reply, "(uso)",
|
||||
(uint32_t) user->uid,
|
||||
user->name,
|
||||
(uint32_t) user->user_record->uid,
|
||||
user->user_record->user_name,
|
||||
p);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@ -1488,7 +1488,7 @@ static int have_multiple_sessions(
|
||||
* count, and non-login sessions do not count either. */
|
||||
HASHMAP_FOREACH(session, m->sessions, i)
|
||||
if (session->class == SESSION_USER &&
|
||||
session->user->uid != uid)
|
||||
session->user->user_record->uid != uid)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
|
@ -119,7 +119,7 @@ int seat_save(Seat *s) {
|
||||
"ACTIVE=%s\n"
|
||||
"ACTIVE_UID="UID_FMT"\n",
|
||||
s->active->id,
|
||||
s->active->user->uid);
|
||||
s->active->user->user_record->uid);
|
||||
}
|
||||
|
||||
if (s->sessions) {
|
||||
@ -137,7 +137,7 @@ int seat_save(Seat *s) {
|
||||
LIST_FOREACH(sessions_by_seat, i, s->sessions)
|
||||
fprintf(f,
|
||||
UID_FMT"%c",
|
||||
i->user->uid,
|
||||
i->user->user_record->uid,
|
||||
i->sessions_by_seat_next ? ' ' : '\n');
|
||||
}
|
||||
|
||||
@ -216,8 +216,8 @@ int seat_apply_acls(Seat *s, Session *old_active) {
|
||||
|
||||
r = devnode_acl_all(s->id,
|
||||
false,
|
||||
!!old_active, old_active ? old_active->user->uid : 0,
|
||||
!!s->active, s->active ? s->active->user->uid : 0);
|
||||
!!old_active, old_active ? old_active->user->user_record->uid : 0,
|
||||
!!s->active, s->active ? s->active->user->user_record->uid : 0);
|
||||
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to apply ACLs: %m");
|
||||
|
@ -43,7 +43,7 @@ static int property_get_user(
|
||||
if (!p)
|
||||
return -ENOMEM;
|
||||
|
||||
return sd_bus_message_append(reply, "(uo)", (uint32_t) s->user->uid, p);
|
||||
return sd_bus_message_append(reply, "(uo)", (uint32_t) s->user->user_record->uid, p);
|
||||
}
|
||||
|
||||
static int property_get_name(
|
||||
@ -61,7 +61,7 @@ static int property_get_name(
|
||||
assert(reply);
|
||||
assert(s);
|
||||
|
||||
return sd_bus_message_append(reply, "s", s->user->name);
|
||||
return sd_bus_message_append(reply, "s", s->user->user_record->user_name);
|
||||
}
|
||||
|
||||
static int property_get_seat(
|
||||
@ -168,7 +168,7 @@ int bus_session_method_terminate(sd_bus_message *message, void *userdata, sd_bus
|
||||
"org.freedesktop.login1.manage",
|
||||
NULL,
|
||||
false,
|
||||
s->user->uid,
|
||||
s->user->user_record->uid,
|
||||
&s->manager->polkit_registry,
|
||||
error);
|
||||
if (r < 0)
|
||||
@ -210,7 +210,7 @@ int bus_session_method_lock(sd_bus_message *message, void *userdata, sd_bus_erro
|
||||
"org.freedesktop.login1.lock-sessions",
|
||||
NULL,
|
||||
false,
|
||||
s->user->uid,
|
||||
s->user->user_record->uid,
|
||||
&s->manager->polkit_registry,
|
||||
error);
|
||||
if (r < 0)
|
||||
@ -246,7 +246,7 @@ static int method_set_idle_hint(sd_bus_message *message, void *userdata, sd_bus_
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (uid != 0 && uid != s->user->uid)
|
||||
if (uid != 0 && uid != s->user->user_record->uid)
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Only owner of session may set idle hint");
|
||||
|
||||
r = session_set_idle_hint(s, b);
|
||||
@ -279,7 +279,7 @@ static int method_set_locked_hint(sd_bus_message *message, void *userdata, sd_bu
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (uid != 0 && uid != s->user->uid)
|
||||
if (uid != 0 && uid != s->user->user_record->uid)
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Only owner of session may set locked hint");
|
||||
|
||||
session_set_locked_hint(s, b);
|
||||
@ -318,7 +318,7 @@ int bus_session_method_kill(sd_bus_message *message, void *userdata, sd_bus_erro
|
||||
"org.freedesktop.login1.manage",
|
||||
NULL,
|
||||
false,
|
||||
s->user->uid,
|
||||
s->user->user_record->uid,
|
||||
&s->manager->polkit_registry,
|
||||
error);
|
||||
if (r < 0)
|
||||
@ -354,7 +354,7 @@ static int method_take_control(sd_bus_message *message, void *userdata, sd_bus_e
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (uid != 0 && (force || uid != s->user->uid))
|
||||
if (uid != 0 && (force || uid != s->user->user_record->uid))
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Only owner of session may take control");
|
||||
|
||||
r = session_set_controller(s, sd_bus_message_get_sender(message), force, true);
|
||||
@ -523,7 +523,7 @@ static int method_set_brightness(sd_bus_message *message, void *userdata, sd_bus
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (uid != 0 && uid != s->user->uid)
|
||||
if (uid != 0 && uid != s->user->user_record->uid)
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Only owner of session may change brightness.");
|
||||
|
||||
r = sd_device_new_from_subsystem_sysname(&d, subsystem, name);
|
||||
@ -822,7 +822,7 @@ int session_send_create_reply(Session *s, sd_bus_error *error) {
|
||||
"session_fd=%d seat=%s vtnr=%u",
|
||||
s->id,
|
||||
p,
|
||||
(uint32_t) s->user->uid,
|
||||
(uint32_t) s->user->user_record->uid,
|
||||
s->user->runtime_path,
|
||||
fifo_fd,
|
||||
s->seat ? s->seat->id : "",
|
||||
@ -834,7 +834,7 @@ int session_send_create_reply(Session *s, sd_bus_error *error) {
|
||||
p,
|
||||
s->user->runtime_path,
|
||||
fifo_fd,
|
||||
(uint32_t) s->user->uid,
|
||||
(uint32_t) s->user->user_record->uid,
|
||||
s->seat ? s->seat->id : "",
|
||||
(uint32_t) s->vtnr,
|
||||
false);
|
||||
|
@ -230,8 +230,8 @@ int session_save(Session *s) {
|
||||
"IS_DISPLAY=%i\n"
|
||||
"STATE=%s\n"
|
||||
"REMOTE=%i\n",
|
||||
s->user->uid,
|
||||
s->user->name,
|
||||
s->user->user_record->uid,
|
||||
s->user->user_record->user_name,
|
||||
session_is_active(s),
|
||||
s->user->display == s,
|
||||
session_state_to_string(session_get_state(s)),
|
||||
@ -641,7 +641,7 @@ static int session_start_scope(Session *s, sd_bus_message *properties, sd_bus_er
|
||||
if (!scope)
|
||||
return log_oom();
|
||||
|
||||
description = strjoina("Session ", s->id, " of user ", s->user->name);
|
||||
description = strjoina("Session ", s->id, " of user ", s->user->user_record->user_name);
|
||||
|
||||
r = manager_start_scope(
|
||||
s->manager,
|
||||
@ -657,7 +657,7 @@ static int session_start_scope(Session *s, sd_bus_message *properties, sd_bus_er
|
||||
"systemd-user-sessions.service",
|
||||
s->user->runtime_dir_service,
|
||||
s->user->service),
|
||||
s->user->home,
|
||||
user_record_home_directory(s->user->user_record),
|
||||
properties,
|
||||
error,
|
||||
&s->scope_job);
|
||||
@ -698,9 +698,9 @@ int session_start(Session *s, sd_bus_message *properties, sd_bus_error *error) {
|
||||
log_struct(s->class == SESSION_BACKGROUND ? LOG_DEBUG : LOG_INFO,
|
||||
"MESSAGE_ID=" SD_MESSAGE_SESSION_START_STR,
|
||||
"SESSION_ID=%s", s->id,
|
||||
"USER_ID=%s", s->user->name,
|
||||
"USER_ID=%s", s->user->user_record->user_name,
|
||||
"LEADER="PID_FMT, s->leader,
|
||||
LOG_MESSAGE("New session %s of user %s.", s->id, s->user->name));
|
||||
LOG_MESSAGE("New session %s of user %s.", s->id, s->user->user_record->user_name));
|
||||
|
||||
if (!dual_timestamp_is_set(&s->timestamp))
|
||||
dual_timestamp_get(&s->timestamp);
|
||||
@ -750,7 +750,10 @@ static int session_stop_scope(Session *s, bool force) {
|
||||
s->scope_job = mfree(s->scope_job);
|
||||
|
||||
/* Optionally, let's kill everything that's left now. */
|
||||
if (force || manager_shall_kill(s->manager, s->user->name)) {
|
||||
if (force ||
|
||||
(s->user->user_record->kill_processes != 0 &&
|
||||
(s->user->user_record->kill_processes > 0 ||
|
||||
manager_shall_kill(s->manager, s->user->user_record->user_name)))) {
|
||||
|
||||
r = manager_stop_unit(s->manager, s->scope, &error, &s->scope_job);
|
||||
if (r < 0) {
|
||||
@ -766,7 +769,7 @@ static int session_stop_scope(Session *s, bool force) {
|
||||
* Session stop is quite significant on its own, let's log it. */
|
||||
log_struct(s->class == SESSION_BACKGROUND ? LOG_DEBUG : LOG_INFO,
|
||||
"SESSION_ID=%s", s->id,
|
||||
"USER_ID=%s", s->user->name,
|
||||
"USER_ID=%s", s->user->user_record->user_name,
|
||||
"LEADER="PID_FMT, s->leader,
|
||||
LOG_MESSAGE("Session %s logged out. Waiting for processes to exit.", s->id));
|
||||
}
|
||||
@ -824,7 +827,7 @@ int session_finalize(Session *s) {
|
||||
log_struct(s->class == SESSION_BACKGROUND ? LOG_DEBUG : LOG_INFO,
|
||||
"MESSAGE_ID=" SD_MESSAGE_SESSION_STOP_STR,
|
||||
"SESSION_ID=%s", s->id,
|
||||
"USER_ID=%s", s->user->name,
|
||||
"USER_ID=%s", s->user->user_record->user_name,
|
||||
"LEADER="PID_FMT, s->leader,
|
||||
LOG_MESSAGE("Removed session %s.", s->id));
|
||||
|
||||
@ -1189,7 +1192,7 @@ static int session_prepare_vt(Session *s) {
|
||||
if (vt < 0)
|
||||
return vt;
|
||||
|
||||
r = fchown(vt, s->user->uid, -1);
|
||||
r = fchown(vt, s->user->user_record->uid, -1);
|
||||
if (r < 0) {
|
||||
r = log_error_errno(errno,
|
||||
"Cannot change owner of /dev/tty%u: %m",
|
||||
|
@ -15,6 +15,60 @@
|
||||
#include "strv.h"
|
||||
#include "user-util.h"
|
||||
|
||||
static int property_get_uid(
|
||||
sd_bus *bus,
|
||||
const char *path,
|
||||
const char *interface,
|
||||
const char *property,
|
||||
sd_bus_message *reply,
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
User *u = userdata;
|
||||
|
||||
assert(bus);
|
||||
assert(reply);
|
||||
assert(u);
|
||||
|
||||
return sd_bus_message_append(reply, "u", (uint32_t) u->user_record->uid);
|
||||
}
|
||||
|
||||
static int property_get_gid(
|
||||
sd_bus *bus,
|
||||
const char *path,
|
||||
const char *interface,
|
||||
const char *property,
|
||||
sd_bus_message *reply,
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
User *u = userdata;
|
||||
|
||||
assert(bus);
|
||||
assert(reply);
|
||||
assert(u);
|
||||
|
||||
return sd_bus_message_append(reply, "u", (uint32_t) u->user_record->gid);
|
||||
}
|
||||
|
||||
static int property_get_name(
|
||||
sd_bus *bus,
|
||||
const char *path,
|
||||
const char *interface,
|
||||
const char *property,
|
||||
sd_bus_message *reply,
|
||||
void *userdata,
|
||||
sd_bus_error *error) {
|
||||
|
||||
User *u = userdata;
|
||||
|
||||
assert(bus);
|
||||
assert(reply);
|
||||
assert(u);
|
||||
|
||||
return sd_bus_message_append(reply, "s", u->user_record->user_name);
|
||||
}
|
||||
|
||||
static BUS_DEFINE_PROPERTY_GET2(property_get_state, "s", User, user_get_state, user_state_to_string);
|
||||
|
||||
static int property_get_display(
|
||||
@ -152,7 +206,7 @@ int bus_user_method_terminate(sd_bus_message *message, void *userdata, sd_bus_er
|
||||
"org.freedesktop.login1.manage",
|
||||
NULL,
|
||||
false,
|
||||
u->uid,
|
||||
u->user_record->uid,
|
||||
&u->manager->polkit_registry,
|
||||
error);
|
||||
if (r < 0)
|
||||
@ -181,7 +235,7 @@ int bus_user_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *
|
||||
"org.freedesktop.login1.manage",
|
||||
NULL,
|
||||
false,
|
||||
u->uid,
|
||||
u->user_record->uid,
|
||||
&u->manager->polkit_registry,
|
||||
error);
|
||||
if (r < 0)
|
||||
@ -206,9 +260,9 @@ int bus_user_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *
|
||||
const sd_bus_vtable user_vtable[] = {
|
||||
SD_BUS_VTABLE_START(0),
|
||||
|
||||
SD_BUS_PROPERTY("UID", "u", bus_property_get_uid, offsetof(User, uid), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("GID", "u", bus_property_get_gid, offsetof(User, gid), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("Name", "s", NULL, offsetof(User, name), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("UID", "u", property_get_uid, 0, SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("GID", "u", property_get_gid, 0, SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("Name", "s", property_get_name, 0, SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
BUS_PROPERTY_DUAL_TIMESTAMP("Timestamp", offsetof(User, timestamp), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("RuntimePath", "s", NULL, offsetof(User, runtime_path), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("Service", "s", NULL, offsetof(User, service), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
@ -276,7 +330,7 @@ char *user_bus_path(User *u) {
|
||||
|
||||
assert(u);
|
||||
|
||||
if (asprintf(&s, "/org/freedesktop/login1/user/_"UID_FMT, u->uid) < 0)
|
||||
if (asprintf(&s, "/org/freedesktop/login1/user/_"UID_FMT, u->user_record->uid) < 0)
|
||||
return NULL;
|
||||
|
||||
return s;
|
||||
@ -345,7 +399,7 @@ int user_send_signal(User *u, bool new_user) {
|
||||
"/org/freedesktop/login1",
|
||||
"org.freedesktop.login1.Manager",
|
||||
new_user ? "UserNew" : "UserRemoved",
|
||||
"uo", (uint32_t) u->uid, p);
|
||||
"uo", (uint32_t) u->user_record->uid, p);
|
||||
}
|
||||
|
||||
int user_send_changed(User *u, const char *properties, ...) {
|
||||
|
@ -37,10 +37,7 @@
|
||||
|
||||
int user_new(User **ret,
|
||||
Manager *m,
|
||||
uid_t uid,
|
||||
gid_t gid,
|
||||
const char *name,
|
||||
const char *home) {
|
||||
UserRecord *ur) {
|
||||
|
||||
_cleanup_(user_freep) User *u = NULL;
|
||||
char lu[DECIMAL_STR_MAX(uid_t) + 1];
|
||||
@ -48,7 +45,13 @@ int user_new(User **ret,
|
||||
|
||||
assert(ret);
|
||||
assert(m);
|
||||
assert(name);
|
||||
assert(ur);
|
||||
|
||||
if (!ur->user_name)
|
||||
return -EINVAL;
|
||||
|
||||
if (!uid_is_valid(ur->uid))
|
||||
return -EINVAL;
|
||||
|
||||
u = new(User, 1);
|
||||
if (!u)
|
||||
@ -56,28 +59,17 @@ int user_new(User **ret,
|
||||
|
||||
*u = (User) {
|
||||
.manager = m,
|
||||
.uid = uid,
|
||||
.gid = gid,
|
||||
.user_record = user_record_ref(ur),
|
||||
.last_session_timestamp = USEC_INFINITY,
|
||||
};
|
||||
|
||||
u->name = strdup(name);
|
||||
if (!u->name)
|
||||
if (asprintf(&u->state_file, "/run/systemd/users/" UID_FMT, ur->uid) < 0)
|
||||
return -ENOMEM;
|
||||
|
||||
u->home = strdup(home);
|
||||
if (!u->home)
|
||||
if (asprintf(&u->runtime_path, "/run/user/" UID_FMT, ur->uid) < 0)
|
||||
return -ENOMEM;
|
||||
|
||||
path_simplify(u->home, true);
|
||||
|
||||
if (asprintf(&u->state_file, "/run/systemd/users/"UID_FMT, uid) < 0)
|
||||
return -ENOMEM;
|
||||
|
||||
if (asprintf(&u->runtime_path, "/run/user/"UID_FMT, uid) < 0)
|
||||
return -ENOMEM;
|
||||
|
||||
xsprintf(lu, UID_FMT, uid);
|
||||
xsprintf(lu, UID_FMT, ur->uid);
|
||||
r = slice_build_subslice(SPECIAL_USER_SLICE, lu, &u->slice);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@ -90,7 +82,7 @@ int user_new(User **ret,
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = hashmap_put(m->users, UID_TO_PTR(uid), u);
|
||||
r = hashmap_put(m->users, UID_TO_PTR(ur->uid), u);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@ -129,9 +121,9 @@ User *user_free(User *u) {
|
||||
if (u->slice)
|
||||
hashmap_remove_value(u->manager->user_units, u->slice, u);
|
||||
|
||||
hashmap_remove_value(u->manager->users, UID_TO_PTR(u->uid), u);
|
||||
hashmap_remove_value(u->manager->users, UID_TO_PTR(u->user_record->uid), u);
|
||||
|
||||
(void) sd_event_source_unref(u->timer_event_source);
|
||||
sd_event_source_unref(u->timer_event_source);
|
||||
|
||||
u->service_job = mfree(u->service_job);
|
||||
|
||||
@ -140,8 +132,8 @@ User *user_free(User *u) {
|
||||
u->slice = mfree(u->slice);
|
||||
u->runtime_path = mfree(u->runtime_path);
|
||||
u->state_file = mfree(u->state_file);
|
||||
u->name = mfree(u->name);
|
||||
u->home = mfree(u->home);
|
||||
|
||||
user_record_unref(u->user_record);
|
||||
|
||||
return mfree(u);
|
||||
}
|
||||
@ -169,7 +161,7 @@ static int user_save_internal(User *u) {
|
||||
"NAME=%s\n"
|
||||
"STATE=%s\n" /* friendly user-facing state */
|
||||
"STOPPING=%s\n", /* low-level state */
|
||||
u->name,
|
||||
u->user_record->user_name,
|
||||
user_state_to_string(user_get_state(u)),
|
||||
yes_no(u->stopping));
|
||||
|
||||
@ -363,6 +355,100 @@ static void user_start_service(User *u) {
|
||||
"Failed to start user service '%s', ignoring: %s", u->service, bus_error_message(&error, r));
|
||||
}
|
||||
|
||||
static int update_slice_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
|
||||
_cleanup_(user_record_unrefp) UserRecord *ur = userdata;
|
||||
|
||||
assert(m);
|
||||
assert(ur);
|
||||
|
||||
if (sd_bus_message_is_method_error(m, NULL)) {
|
||||
log_warning_errno(sd_bus_message_get_errno(m),
|
||||
"Failed to update slice of %s, ignoring: %s",
|
||||
ur->user_name,
|
||||
sd_bus_message_get_error(m)->message);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
log_debug("Successfully set slice parameters of %s.", ur->user_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int user_update_slice(User *u) {
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
|
||||
int r;
|
||||
|
||||
assert(u);
|
||||
|
||||
if (u->user_record->tasks_max == UINT64_MAX &&
|
||||
u->user_record->memory_high == UINT64_MAX &&
|
||||
u->user_record->memory_max == UINT64_MAX &&
|
||||
u->user_record->cpu_weight == UINT64_MAX &&
|
||||
u->user_record->io_weight == UINT64_MAX)
|
||||
return 0;
|
||||
|
||||
r = sd_bus_message_new_method_call(
|
||||
u->manager->bus,
|
||||
&m,
|
||||
"org.freedesktop.systemd1",
|
||||
"/org/freedesktop/systemd1",
|
||||
"org.freedesktop.systemd1.Manager",
|
||||
"SetUnitProperties");
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
r = sd_bus_message_append(m, "sb", u->slice, true);
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
r = sd_bus_message_open_container(m, 'a', "(sv)");
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
if (u->user_record->tasks_max != UINT64_MAX) {
|
||||
r = sd_bus_message_append(m, "(sv)", "TasksMax", "t", u->user_record->tasks_max);
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
}
|
||||
|
||||
if (u->user_record->memory_max != UINT64_MAX) {
|
||||
r = sd_bus_message_append(m, "(sv)", "MemoryMax", "t", u->user_record->memory_max);
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
}
|
||||
|
||||
if (u->user_record->memory_high != UINT64_MAX) {
|
||||
r = sd_bus_message_append(m, "(sv)", "MemoryHigh", "t", u->user_record->memory_high);
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
}
|
||||
|
||||
if (u->user_record->cpu_weight != UINT64_MAX) {
|
||||
r = sd_bus_message_append(m, "(sv)", "CPUWeight", "t", u->user_record->cpu_weight);
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
}
|
||||
|
||||
if (u->user_record->io_weight != UINT64_MAX) {
|
||||
r = sd_bus_message_append(m, "(sv)", "IOWeight", "t", u->user_record->io_weight);
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
}
|
||||
|
||||
r = sd_bus_message_close_container(m);
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
r = sd_bus_call_async(u->manager->bus, NULL, m, update_slice_callback, u->user_record, 0);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to change user slice properties: %m");
|
||||
|
||||
/* Ref the user record pointer, so that the slot keeps it pinned */
|
||||
user_record_ref(u->user_record);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int user_start(User *u) {
|
||||
assert(u);
|
||||
|
||||
@ -376,12 +462,15 @@ int user_start(User *u) {
|
||||
u->stopping = false;
|
||||
|
||||
if (!u->started)
|
||||
log_debug("Starting services for new user %s.", u->name);
|
||||
log_debug("Starting services for new user %s.", u->user_record->user_name);
|
||||
|
||||
/* Save the user data so far, because pam_systemd will read the XDG_RUNTIME_DIR out of it while starting up
|
||||
* systemd --user. We need to do user_save_internal() because we have not "officially" started yet. */
|
||||
user_save_internal(u);
|
||||
|
||||
/* Set slice parameters */
|
||||
(void) user_update_slice(u);
|
||||
|
||||
/* Start user@UID.service */
|
||||
user_start_service(u);
|
||||
|
||||
@ -460,7 +549,7 @@ int user_finalize(User *u) {
|
||||
* done. This is called as a result of an earlier user_done() when all jobs are completed. */
|
||||
|
||||
if (u->started)
|
||||
log_debug("User %s logged out.", u->name);
|
||||
log_debug("User %s logged out.", u->user_record->user_name);
|
||||
|
||||
LIST_FOREACH(sessions_by_user, s, u->sessions) {
|
||||
k = session_finalize(s);
|
||||
@ -474,8 +563,8 @@ int user_finalize(User *u) {
|
||||
* cases, as we shouldn't accidentally remove a system service's IPC objects while it is running, just because
|
||||
* a cronjob running as the same user just finished. Hence: exclude system users generally from IPC clean-up,
|
||||
* and do it only for normal users. */
|
||||
if (u->manager->remove_ipc && !uid_is_system(u->uid)) {
|
||||
k = clean_ipc_by_uid(u->uid);
|
||||
if (u->manager->remove_ipc && !uid_is_system(u->user_record->uid)) {
|
||||
k = clean_ipc_by_uid(u->user_record->uid);
|
||||
if (k < 0)
|
||||
r = k;
|
||||
}
|
||||
@ -531,7 +620,7 @@ int user_check_linger_file(User *u) {
|
||||
_cleanup_free_ char *cc = NULL;
|
||||
char *p = NULL;
|
||||
|
||||
cc = cescape(u->name);
|
||||
cc = cescape(u->user_record->user_name);
|
||||
if (!cc)
|
||||
return -ENOMEM;
|
||||
|
||||
@ -567,6 +656,18 @@ static bool user_unit_active(User *u) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static usec_t user_get_stop_delay(User *u) {
|
||||
assert(u);
|
||||
|
||||
if (u->user_record->stop_delay_usec != UINT64_MAX)
|
||||
return u->user_record->stop_delay_usec;
|
||||
|
||||
if (user_record_removable(u->user_record) > 0)
|
||||
return 0; /* For removable users lower the stop delay to zero */
|
||||
|
||||
return u->manager->user_stop_delay;
|
||||
}
|
||||
|
||||
bool user_may_gc(User *u, bool drop_not_started) {
|
||||
int r;
|
||||
|
||||
@ -579,12 +680,16 @@ bool user_may_gc(User *u, bool drop_not_started) {
|
||||
return false;
|
||||
|
||||
if (u->last_session_timestamp != USEC_INFINITY) {
|
||||
usec_t user_stop_delay;
|
||||
|
||||
/* All sessions have been closed. Let's see if we shall leave the user record around for a bit */
|
||||
|
||||
if (u->manager->user_stop_delay == USEC_INFINITY)
|
||||
user_stop_delay = user_get_stop_delay(u);
|
||||
|
||||
if (user_stop_delay == USEC_INFINITY)
|
||||
return false; /* Leave it around forever! */
|
||||
if (u->manager->user_stop_delay > 0 &&
|
||||
now(CLOCK_MONOTONIC) < usec_add(u->last_session_timestamp, u->manager->user_stop_delay))
|
||||
if (user_stop_delay > 0 &&
|
||||
now(CLOCK_MONOTONIC) < usec_add(u->last_session_timestamp, user_stop_delay))
|
||||
return false; /* Leave it around for a bit longer. */
|
||||
}
|
||||
|
||||
@ -712,7 +817,7 @@ void user_elect_display(User *u) {
|
||||
|
||||
/* This elects a primary session for each user, which we call the "display". We try to keep the assignment
|
||||
* stable, but we "upgrade" to better choices. */
|
||||
log_debug("Electing new display for user %s", u->name);
|
||||
log_debug("Electing new display for user %s", u->user_record->user_name);
|
||||
|
||||
LIST_FOREACH(sessions_by_user, s, u->sessions) {
|
||||
if (!elect_display_filter(s)) {
|
||||
@ -737,6 +842,7 @@ static int user_stop_timeout_callback(sd_event_source *es, uint64_t usec, void *
|
||||
}
|
||||
|
||||
void user_update_last_session_timer(User *u) {
|
||||
usec_t user_stop_delay;
|
||||
int r;
|
||||
|
||||
assert(u);
|
||||
@ -755,7 +861,8 @@ void user_update_last_session_timer(User *u) {
|
||||
|
||||
assert(!u->timer_event_source);
|
||||
|
||||
if (IN_SET(u->manager->user_stop_delay, 0, USEC_INFINITY))
|
||||
user_stop_delay = user_get_stop_delay(u);
|
||||
if (IN_SET(user_stop_delay, 0, USEC_INFINITY))
|
||||
return;
|
||||
|
||||
if (sd_event_get_state(u->manager->event) == SD_EVENT_FINISHED) {
|
||||
@ -766,7 +873,7 @@ void user_update_last_session_timer(User *u) {
|
||||
r = sd_event_add_time(u->manager->event,
|
||||
&u->timer_event_source,
|
||||
CLOCK_MONOTONIC,
|
||||
usec_add(u->last_session_timestamp, u->manager->user_stop_delay), 0,
|
||||
usec_add(u->last_session_timestamp, user_stop_delay), 0,
|
||||
user_stop_timeout_callback, u);
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to enqueue user stop event source, ignoring: %m");
|
||||
@ -775,8 +882,8 @@ void user_update_last_session_timer(User *u) {
|
||||
char s[FORMAT_TIMESPAN_MAX];
|
||||
|
||||
log_debug("Last session of user '%s' logged out, terminating user context in %s.",
|
||||
u->name,
|
||||
format_timespan(s, sizeof(s), u->manager->user_stop_delay, USEC_PER_MSEC));
|
||||
u->user_record->user_name,
|
||||
format_timespan(s, sizeof(s), user_stop_delay, USEC_PER_MSEC));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ typedef struct User User;
|
||||
#include "conf-parser.h"
|
||||
#include "list.h"
|
||||
#include "logind.h"
|
||||
#include "user-record.h"
|
||||
|
||||
typedef enum UserState {
|
||||
USER_OFFLINE, /* Not logged in at all */
|
||||
@ -20,10 +21,9 @@ typedef enum UserState {
|
||||
|
||||
struct User {
|
||||
Manager *manager;
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
char *name;
|
||||
char *home;
|
||||
|
||||
UserRecord *user_record;
|
||||
|
||||
char *state_file;
|
||||
char *runtime_path;
|
||||
|
||||
@ -50,7 +50,7 @@ struct User {
|
||||
LIST_FIELDS(User, gc_queue);
|
||||
};
|
||||
|
||||
int user_new(User **out, Manager *m, uid_t uid, gid_t gid, const char *name, const char *home);
|
||||
int user_new(User **out, Manager *m, UserRecord *ur);
|
||||
User *user_free(User *u);
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(User *, user_free);
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "list.h"
|
||||
#include "set.h"
|
||||
#include "time-util.h"
|
||||
#include "user-record.h"
|
||||
|
||||
typedef struct Manager Manager;
|
||||
|
||||
@ -28,7 +29,7 @@ struct Manager {
|
||||
Hashmap *seats;
|
||||
Hashmap *sessions;
|
||||
Hashmap *sessions_by_leader;
|
||||
Hashmap *users;
|
||||
Hashmap *users; /* indexed by UID */
|
||||
Hashmap *inhibitors;
|
||||
Hashmap *buttons;
|
||||
Hashmap *brightness_writers;
|
||||
@ -130,7 +131,7 @@ int manager_add_device(Manager *m, const char *sysfs, bool master, Device **ret_
|
||||
int manager_add_button(Manager *m, const char *name, Button **ret_button);
|
||||
int manager_add_seat(Manager *m, const char *id, Seat **ret_seat);
|
||||
int manager_add_session(Manager *m, const char *id, Session **ret_session);
|
||||
int manager_add_user(Manager *m, uid_t uid, gid_t gid, const char *name, const char *home, User **ret_user);
|
||||
int manager_add_user(Manager *m, UserRecord *ur, User **ret_user);
|
||||
int manager_add_user_by_name(Manager *m, const char *name, User **ret_user);
|
||||
int manager_add_user_by_uid(Manager *m, uid_t uid, User **ret_user);
|
||||
int manager_add_inhibitor(Manager *m, const char* id, Inhibitor **ret_inhibitor);
|
||||
|
@ -25,16 +25,22 @@
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "format-util.h"
|
||||
#include "fs-util.h"
|
||||
#include "hostname-util.h"
|
||||
#include "locale-util.h"
|
||||
#include "login-util.h"
|
||||
#include "macro.h"
|
||||
#include "pam-util.h"
|
||||
#include "parse-util.h"
|
||||
#include "path-util.h"
|
||||
#include "process-util.h"
|
||||
#include "rlimit-util.h"
|
||||
#include "socket-util.h"
|
||||
#include "stdio-util.h"
|
||||
#include "strv.h"
|
||||
#include "terminal-util.h"
|
||||
#include "user-util.h"
|
||||
#include "userdb.h"
|
||||
|
||||
#define LOGIN_SLOW_BUS_CALL_TIMEOUT_USEC (2*USEC_PER_MINUTE)
|
||||
|
||||
@ -86,18 +92,15 @@ static int parse_argv(
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int get_user_data(
|
||||
static int acquire_user_record(
|
||||
pam_handle_t *handle,
|
||||
const char **ret_username,
|
||||
struct passwd **ret_pw) {
|
||||
UserRecord **ret_record) {
|
||||
|
||||
const char *username = NULL;
|
||||
struct passwd *pw = NULL;
|
||||
_cleanup_(user_record_unrefp) UserRecord *ur = NULL;
|
||||
const char *username = NULL, *json = NULL;
|
||||
int r;
|
||||
|
||||
assert(handle);
|
||||
assert(ret_username);
|
||||
assert(ret_pw);
|
||||
|
||||
r = pam_get_user(handle, &username, NULL);
|
||||
if (r != PAM_SUCCESS) {
|
||||
@ -107,17 +110,75 @@ static int get_user_data(
|
||||
|
||||
if (isempty(username)) {
|
||||
pam_syslog(handle, LOG_ERR, "User name not valid.");
|
||||
return PAM_AUTH_ERR;
|
||||
return PAM_SERVICE_ERR;
|
||||
}
|
||||
|
||||
pw = pam_modutil_getpwnam(handle, username);
|
||||
if (!pw) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to get user data.");
|
||||
return PAM_USER_UNKNOWN;
|
||||
/* If pam_systemd_homed (or some other module) already acqired the user record we can reuse it
|
||||
* here. */
|
||||
r = pam_get_data(handle, "systemd-user-record", (const void**) &json);
|
||||
if (r != PAM_SUCCESS || !json) {
|
||||
_cleanup_free_ char *formatted = NULL;
|
||||
|
||||
if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA)) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to get PAM user record data: %s", pam_strerror(handle, r));
|
||||
return r;
|
||||
}
|
||||
|
||||
/* Request the record ourselves */
|
||||
r = userdb_by_name(username, 0, &ur);
|
||||
if (r < 0) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to get user record: %s", strerror_safe(r));
|
||||
return PAM_USER_UNKNOWN;
|
||||
}
|
||||
|
||||
r = json_variant_format(ur->json, 0, &formatted);
|
||||
if (r < 0) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to format user JSON: %s", strerror_safe(r));
|
||||
return PAM_SERVICE_ERR;
|
||||
}
|
||||
|
||||
/* And cache it for everyone else */
|
||||
r = pam_set_data(handle, "systemd-user-record", formatted, pam_cleanup_free);
|
||||
if (r < 0) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to set PAM user record data: %s", pam_strerror(handle, r));
|
||||
return r;
|
||||
}
|
||||
|
||||
TAKE_PTR(formatted);
|
||||
} else {
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
|
||||
|
||||
/* Parse cached record */
|
||||
r = json_parse(json, JSON_PARSE_SENSITIVE, &v, NULL, NULL);
|
||||
if (r < 0) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to parse JSON user record: %s", strerror_safe(r));
|
||||
return PAM_SERVICE_ERR;
|
||||
}
|
||||
|
||||
ur = user_record_new();
|
||||
if (!ur)
|
||||
return pam_log_oom(handle);
|
||||
|
||||
r = user_record_load(ur, v, USER_RECORD_LOAD_REFUSE_SECRET);
|
||||
if (r < 0) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to load user record: %s", strerror_safe(r));
|
||||
return PAM_SERVICE_ERR;
|
||||
}
|
||||
|
||||
/* Safety check if cached record actually matches what we are looking for */
|
||||
if (!streq_ptr(username, ur->user_name)) {
|
||||
pam_syslog(handle, LOG_ERR, "Acquired user record does not match user name.");
|
||||
return PAM_SERVICE_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
*ret_pw = pw;
|
||||
*ret_username = username;
|
||||
if (!uid_is_valid(ur->uid)) {
|
||||
pam_syslog(handle, LOG_ERR, "Acquired user record does not have a UID.");
|
||||
return PAM_SERVICE_ERR;
|
||||
}
|
||||
|
||||
if (ret_record)
|
||||
*ret_record = TAKE_PTR(ur);
|
||||
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
@ -233,17 +294,15 @@ static int export_legacy_dbus_address(
|
||||
return PAM_SUCCESS;
|
||||
|
||||
if (asprintf(&t, DEFAULT_USER_BUS_ADDRESS_FMT, runtime) < 0)
|
||||
goto error;
|
||||
return pam_log_oom(handle);
|
||||
|
||||
r = pam_misc_setenv(handle, "DBUS_SESSION_BUS_ADDRESS", t, 0);
|
||||
if (r != PAM_SUCCESS)
|
||||
goto error;
|
||||
if (r != PAM_SUCCESS) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to set bus variable: %s", pam_strerror(handle, r));
|
||||
return r;
|
||||
}
|
||||
|
||||
return PAM_SUCCESS;
|
||||
|
||||
error:
|
||||
pam_syslog(handle, LOG_ERR, "Failed to set bus variable.");
|
||||
return r;
|
||||
}
|
||||
|
||||
static int append_session_memory_max(pam_handle_t *handle, sd_bus_message *m, const char *limit) {
|
||||
@ -251,36 +310,36 @@ static int append_session_memory_max(pam_handle_t *handle, sd_bus_message *m, co
|
||||
int r;
|
||||
|
||||
if (isempty(limit))
|
||||
return 0;
|
||||
return PAM_SUCCESS;
|
||||
|
||||
if (streq(limit, "infinity")) {
|
||||
r = sd_bus_message_append(m, "(sv)", "MemoryMax", "t", (uint64_t)-1);
|
||||
if (r < 0) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to append to bus message: %s", strerror_safe(r));
|
||||
return r;
|
||||
}
|
||||
} else {
|
||||
r = parse_permille(limit);
|
||||
if (r >= 0) {
|
||||
r = sd_bus_message_append(m, "(sv)", "MemoryMaxScale", "u", (uint32_t) (((uint64_t) r * UINT32_MAX) / 1000U));
|
||||
if (r < 0) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to append to bus message: %s", strerror_safe(r));
|
||||
return r;
|
||||
}
|
||||
} else {
|
||||
r = parse_size(limit, 1024, &val);
|
||||
if (r >= 0) {
|
||||
r = sd_bus_message_append(m, "(sv)", "MemoryMax", "t", val);
|
||||
if (r < 0) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to append to bus message: %s", strerror_safe(r));
|
||||
return r;
|
||||
}
|
||||
} else
|
||||
pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.memory_max: %s, ignoring.", limit);
|
||||
}
|
||||
if (r < 0)
|
||||
return pam_bus_log_create_error(handle, r);
|
||||
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
|
||||
return 0;
|
||||
r = parse_permille(limit);
|
||||
if (r >= 0) {
|
||||
r = sd_bus_message_append(m, "(sv)", "MemoryMaxScale", "u", (uint32_t) (((uint64_t) r * UINT32_MAX) / 1000U));
|
||||
if (r < 0)
|
||||
return pam_bus_log_create_error(handle, r);
|
||||
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
|
||||
r = parse_size(limit, 1024, &val);
|
||||
if (r >= 0) {
|
||||
r = sd_bus_message_append(m, "(sv)", "MemoryMax", "t", val);
|
||||
if (r < 0)
|
||||
return pam_bus_log_create_error(handle, r);
|
||||
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
|
||||
pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.memory_max, ignoring: %s", limit);
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
|
||||
static int append_session_runtime_max_sec(pam_handle_t *handle, sd_bus_message *m, const char *limit) {
|
||||
@ -289,19 +348,17 @@ static int append_session_runtime_max_sec(pam_handle_t *handle, sd_bus_message *
|
||||
|
||||
/* No need to parse "infinity" here, it will be set by default later in scope_init() */
|
||||
if (isempty(limit) || streq(limit, "infinity"))
|
||||
return 0;
|
||||
return PAM_SUCCESS;
|
||||
|
||||
r = parse_sec(limit, &val);
|
||||
if (r >= 0) {
|
||||
r = sd_bus_message_append(m, "(sv)", "RuntimeMaxUSec", "t", (uint64_t) val);
|
||||
if (r < 0) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to append to bus message: %s", strerror_safe(r));
|
||||
return r;
|
||||
}
|
||||
if (r < 0)
|
||||
return pam_bus_log_create_error(handle, r);
|
||||
} else
|
||||
pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.runtime_max_sec: %s, ignoring.", limit);
|
||||
|
||||
return 0;
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
|
||||
static int append_session_tasks_max(pam_handle_t *handle, sd_bus_message *m, const char *limit) {
|
||||
@ -310,19 +367,17 @@ static int append_session_tasks_max(pam_handle_t *handle, sd_bus_message *m, con
|
||||
|
||||
/* No need to parse "infinity" here, it will be set unconditionally later in manager_start_scope() */
|
||||
if (isempty(limit) || streq(limit, "infinity"))
|
||||
return 0;
|
||||
return PAM_SUCCESS;
|
||||
|
||||
r = safe_atou64(limit, &val);
|
||||
if (r >= 0) {
|
||||
r = sd_bus_message_append(m, "(sv)", "TasksMax", "t", val);
|
||||
if (r < 0) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to append to bus message: %s", strerror_safe(r));
|
||||
return r;
|
||||
}
|
||||
if (r < 0)
|
||||
return pam_bus_log_create_error(handle, r);
|
||||
} else
|
||||
pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.tasks_max: %s, ignoring.", limit);
|
||||
pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.tasks_max, ignoring: %s", limit);
|
||||
|
||||
return 0;
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
|
||||
static int append_session_cg_weight(pam_handle_t *handle, sd_bus_message *m, const char *limit, const char *field) {
|
||||
@ -330,21 +385,19 @@ static int append_session_cg_weight(pam_handle_t *handle, sd_bus_message *m, con
|
||||
int r;
|
||||
|
||||
if (isempty(limit))
|
||||
return 0;
|
||||
return PAM_SUCCESS;
|
||||
|
||||
r = cg_weight_parse(limit, &val);
|
||||
if (r >= 0) {
|
||||
r = sd_bus_message_append(m, "(sv)", field, "t", val);
|
||||
if (r < 0) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to append to bus message: %s", strerror_safe(r));
|
||||
return r;
|
||||
}
|
||||
if (r < 0)
|
||||
return pam_bus_log_create_error(handle, r);
|
||||
} else if (streq(field, "CPUWeight"))
|
||||
pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.cpu_weight: %s, ignoring.", limit);
|
||||
pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.cpu_weight, ignoring: %s", limit);
|
||||
else
|
||||
pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.io_weight: %s, ignoring.", limit);
|
||||
pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.io_weight, ignoring: %s", limit);
|
||||
|
||||
return 0;
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
|
||||
static const char* getenv_harder(pam_handle_t *handle, const char *key, const char *fallback) {
|
||||
@ -423,6 +476,138 @@ fail:
|
||||
return false;
|
||||
}
|
||||
|
||||
static int pam_putenv_and_log(pam_handle_t *handle, const char *e, bool debug) {
|
||||
int r;
|
||||
|
||||
assert(handle);
|
||||
assert(e);
|
||||
|
||||
r = pam_putenv(handle, e);
|
||||
if (r != PAM_SUCCESS) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to set PAM environment variable %s: %s", e, pam_strerror(handle, r));
|
||||
return r;
|
||||
}
|
||||
|
||||
if (debug)
|
||||
pam_syslog(handle, LOG_DEBUG, "PAM environment variable %s set based on user record.", e);
|
||||
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
|
||||
static int apply_user_record_settings(pam_handle_t *handle, UserRecord *ur, bool debug) {
|
||||
char **i;
|
||||
int r;
|
||||
|
||||
assert(handle);
|
||||
assert(ur);
|
||||
|
||||
if (ur->umask != MODE_INVALID) {
|
||||
umask(ur->umask);
|
||||
|
||||
if (debug)
|
||||
pam_syslog(handle, LOG_DEBUG, "Set user umask to %04o based on user record.", ur->umask);
|
||||
}
|
||||
|
||||
STRV_FOREACH(i, ur->environment) {
|
||||
_cleanup_free_ char *n = NULL;
|
||||
const char *e;
|
||||
|
||||
assert_se(e = strchr(*i, '=')); /* environment was already validated while parsing JSON record, this thus must hold */
|
||||
|
||||
n = strndup(*i, e - *i);
|
||||
if (!n)
|
||||
return pam_log_oom(handle);
|
||||
|
||||
if (pam_getenv(handle, n)) {
|
||||
if (debug)
|
||||
pam_syslog(handle, LOG_DEBUG, "PAM environment variable $%s already set, not changing based on record.", *i);
|
||||
continue;
|
||||
}
|
||||
|
||||
r = pam_putenv_and_log(handle, *i, debug);
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (ur->email_address) {
|
||||
if (pam_getenv(handle, "EMAIL")) {
|
||||
if (debug)
|
||||
pam_syslog(handle, LOG_DEBUG, "PAM environment variable $EMAIL already set, not changing based on user record.");
|
||||
} else {
|
||||
_cleanup_free_ char *joined = NULL;
|
||||
|
||||
joined = strjoin("EMAIL=", ur->email_address);
|
||||
if (!joined)
|
||||
return pam_log_oom(handle);
|
||||
|
||||
r = pam_putenv_and_log(handle, joined, debug);
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
if (ur->time_zone) {
|
||||
if (pam_getenv(handle, "TZ")) {
|
||||
if (debug)
|
||||
pam_syslog(handle, LOG_DEBUG, "PAM environment variable $TZ already set, not changing based on user record.");
|
||||
} else if (!timezone_is_valid(ur->time_zone, LOG_DEBUG)) {
|
||||
if (debug)
|
||||
pam_syslog(handle, LOG_DEBUG, "Time zone specified in user record is not valid locally, not setting $TZ.");
|
||||
} else {
|
||||
_cleanup_free_ char *joined = NULL;
|
||||
|
||||
joined = strjoin("TZ=:", ur->time_zone);
|
||||
if (!joined)
|
||||
return pam_log_oom(handle);
|
||||
|
||||
r = pam_putenv_and_log(handle, joined, debug);
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
if (ur->preferred_language) {
|
||||
if (pam_getenv(handle, "LANG")) {
|
||||
if (debug)
|
||||
pam_syslog(handle, LOG_DEBUG, "PAM environment variable $LANG already set, not changing based on user record.");
|
||||
} else if (!locale_is_valid(ur->preferred_language)) {
|
||||
if (debug)
|
||||
pam_syslog(handle, LOG_DEBUG, "Preferred language specified in user record is not valid locally, not setting $LANG.");
|
||||
} else {
|
||||
_cleanup_free_ char *joined = NULL;
|
||||
|
||||
joined = strjoin("LANG=", ur->preferred_language);
|
||||
if (!joined)
|
||||
return pam_log_oom(handle);
|
||||
|
||||
r = pam_putenv_and_log(handle, joined, debug);
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
if (nice_is_valid(ur->nice_level)) {
|
||||
if (nice(ur->nice_level) < 0)
|
||||
pam_syslog(handle, LOG_ERR, "Failed to set nice level to %i, ignoring: %s", ur->nice_level, strerror_safe(errno));
|
||||
else if (debug)
|
||||
pam_syslog(handle, LOG_DEBUG, "Nice level set, based on user record.");
|
||||
}
|
||||
|
||||
for (int rl = 0; rl < _RLIMIT_MAX; rl++) {
|
||||
|
||||
if (!ur->rlimits[rl])
|
||||
continue;
|
||||
|
||||
r = setrlimit_closest(rl, ur->rlimits[rl]);
|
||||
if (r < 0)
|
||||
pam_syslog(handle, LOG_ERR, "Failed to set resource limit %s, ignoring: %s", rlimit_to_string(rl), strerror_safe(r));
|
||||
else if (debug)
|
||||
pam_syslog(handle, LOG_DEBUG, "Resource limit %s set, based on user record.", rlimit_to_string(rl));
|
||||
}
|
||||
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
|
||||
_public_ PAM_EXTERN int pam_sm_open_session(
|
||||
pam_handle_t *handle,
|
||||
int flags,
|
||||
@ -431,7 +616,7 @@ _public_ PAM_EXTERN int pam_sm_open_session(
|
||||
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
|
||||
const char
|
||||
*username, *id, *object_path, *runtime_path,
|
||||
*id, *object_path, *runtime_path,
|
||||
*service = NULL,
|
||||
*tty = NULL, *display = NULL,
|
||||
*remote_user = NULL, *remote_host = NULL,
|
||||
@ -440,9 +625,9 @@ _public_ PAM_EXTERN int pam_sm_open_session(
|
||||
*class_pam = NULL, *type_pam = NULL, *cvtnr = NULL, *desktop = NULL, *desktop_pam = NULL,
|
||||
*memory_max = NULL, *tasks_max = NULL, *cpu_weight = NULL, *io_weight = NULL, *runtime_max_sec = NULL;
|
||||
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
|
||||
_cleanup_(user_record_unrefp) UserRecord *ur = NULL;
|
||||
int session_fd = -1, existing, r;
|
||||
bool debug = false, remote;
|
||||
struct passwd *pw;
|
||||
uint32_t vtnr = 0;
|
||||
uid_t original_uid;
|
||||
|
||||
@ -463,7 +648,7 @@ _public_ PAM_EXTERN int pam_sm_open_session(
|
||||
if (debug)
|
||||
pam_syslog(handle, LOG_DEBUG, "pam-systemd initializing");
|
||||
|
||||
r = get_user_data(handle, &username, &pw);
|
||||
r = acquire_user_record(handle, &ur);
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
|
||||
@ -477,8 +662,8 @@ _public_ PAM_EXTERN int pam_sm_open_session(
|
||||
if (streq_ptr(service, "systemd-user")) {
|
||||
char rt[STRLEN("/run/user/") + DECIMAL_STR_MAX(uid_t)];
|
||||
|
||||
xsprintf(rt, "/run/user/"UID_FMT, pw->pw_uid);
|
||||
if (validate_runtime_directory(handle, rt, pw->pw_uid)) {
|
||||
xsprintf(rt, "/run/user/"UID_FMT, ur->uid);
|
||||
if (validate_runtime_directory(handle, rt, ur->uid)) {
|
||||
r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
|
||||
if (r != PAM_SUCCESS) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to set runtime dir: %s", pam_strerror(handle, r));
|
||||
@ -486,7 +671,11 @@ _public_ PAM_EXTERN int pam_sm_open_session(
|
||||
}
|
||||
}
|
||||
|
||||
r = export_legacy_dbus_address(handle, pw->pw_uid, rt);
|
||||
r = export_legacy_dbus_address(handle, ur->uid, rt);
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
|
||||
r = apply_user_record_settings(handle, ur, debug);
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
|
||||
@ -573,16 +762,14 @@ _public_ PAM_EXTERN int pam_sm_open_session(
|
||||
|
||||
/* Talk to logind over the message bus */
|
||||
|
||||
r = sd_bus_open_system(&bus);
|
||||
if (r < 0) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror_safe(r));
|
||||
return PAM_SESSION_ERR;
|
||||
}
|
||||
r = pam_acquire_bus_connection(handle, &bus);
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
|
||||
if (debug) {
|
||||
pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
|
||||
"uid="UID_FMT" pid="PID_FMT" service=%s type=%s class=%s desktop=%s seat=%s vtnr=%"PRIu32" tty=%s display=%s remote=%s remote_user=%s remote_host=%s",
|
||||
pw->pw_uid, getpid_cached(),
|
||||
ur->uid, getpid_cached(),
|
||||
strempty(service),
|
||||
type, class, strempty(desktop),
|
||||
strempty(seat), vtnr, strempty(tty), strempty(display),
|
||||
@ -599,13 +786,11 @@ _public_ PAM_EXTERN int pam_sm_open_session(
|
||||
"/org/freedesktop/login1",
|
||||
"org.freedesktop.login1.Manager",
|
||||
"CreateSession");
|
||||
if (r < 0) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to create CreateSession method call: %s", strerror_safe(r));
|
||||
return PAM_SESSION_ERR;
|
||||
}
|
||||
if (r < 0)
|
||||
return pam_bus_log_create_error(handle, r);
|
||||
|
||||
r = sd_bus_message_append(m, "uusssssussbss",
|
||||
(uint32_t) pw->pw_uid,
|
||||
(uint32_t) ur->uid,
|
||||
0,
|
||||
service,
|
||||
type,
|
||||
@ -618,42 +803,36 @@ _public_ PAM_EXTERN int pam_sm_open_session(
|
||||
remote,
|
||||
remote_user,
|
||||
remote_host);
|
||||
if (r < 0) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to append to bus message: %s", strerror_safe(r));
|
||||
return PAM_SESSION_ERR;
|
||||
}
|
||||
if (r < 0)
|
||||
return pam_bus_log_create_error(handle, r);
|
||||
|
||||
r = sd_bus_message_open_container(m, 'a', "(sv)");
|
||||
if (r < 0) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to open message container: %s", strerror_safe(r));
|
||||
return PAM_SYSTEM_ERR;
|
||||
}
|
||||
if (r < 0)
|
||||
return pam_bus_log_create_error(handle, r);
|
||||
|
||||
r = append_session_memory_max(handle, m, memory_max);
|
||||
if (r < 0)
|
||||
return PAM_SESSION_ERR;
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
|
||||
r = append_session_runtime_max_sec(handle, m, runtime_max_sec);
|
||||
if (r < 0)
|
||||
return PAM_SESSION_ERR;
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
|
||||
r = append_session_tasks_max(handle, m, tasks_max);
|
||||
if (r < 0)
|
||||
return PAM_SESSION_ERR;
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
|
||||
r = append_session_cg_weight(handle, m, cpu_weight, "CPUWeight");
|
||||
if (r < 0)
|
||||
return PAM_SESSION_ERR;
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
|
||||
r = append_session_cg_weight(handle, m, io_weight, "IOWeight");
|
||||
if (r < 0)
|
||||
return PAM_SESSION_ERR;
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
|
||||
r = sd_bus_message_close_container(m);
|
||||
if (r < 0) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to close message container: %s", strerror_safe(r));
|
||||
return PAM_SYSTEM_ERR;
|
||||
}
|
||||
if (r < 0)
|
||||
return pam_bus_log_create_error(handle, r);
|
||||
|
||||
r = sd_bus_call(bus, m, LOGIN_SLOW_BUS_CALL_TIMEOUT_USEC, &error, &reply);
|
||||
if (r < 0) {
|
||||
@ -677,10 +856,8 @@ _public_ PAM_EXTERN int pam_sm_open_session(
|
||||
&seat,
|
||||
&vtnr,
|
||||
&existing);
|
||||
if (r < 0) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", strerror_safe(r));
|
||||
return PAM_SESSION_ERR;
|
||||
}
|
||||
if (r < 0)
|
||||
return pam_bus_log_parse_error(handle, r);
|
||||
|
||||
if (debug)
|
||||
pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
|
||||
@ -691,20 +868,20 @@ _public_ PAM_EXTERN int pam_sm_open_session(
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
|
||||
if (original_uid == pw->pw_uid) {
|
||||
if (original_uid == ur->uid) {
|
||||
/* Don't set $XDG_RUNTIME_DIR if the user we now
|
||||
* authenticated for does not match the original user
|
||||
* of the session. We do this in order not to result
|
||||
* in privileged apps clobbering the runtime directory
|
||||
* unnecessarily. */
|
||||
|
||||
if (validate_runtime_directory(handle, runtime_path, pw->pw_uid)) {
|
||||
if (validate_runtime_directory(handle, runtime_path, ur->uid)) {
|
||||
r = update_environment(handle, "XDG_RUNTIME_DIR", runtime_path);
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
}
|
||||
|
||||
r = export_legacy_dbus_address(handle, pw->pw_uid, runtime_path);
|
||||
r = export_legacy_dbus_address(handle, ur->uid, runtime_path);
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
}
|
||||
@ -759,6 +936,14 @@ _public_ PAM_EXTERN int pam_sm_open_session(
|
||||
}
|
||||
}
|
||||
|
||||
r = apply_user_record_settings(handle, ur, debug);
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
|
||||
/* Let's release the D-Bus connection, after all the session might live quite a long time, and we are
|
||||
* not going to use the bus connection in that time, so let's better close before the daemon kicks us
|
||||
* off because we are not processing anything. */
|
||||
(void) pam_release_bus_connection(handle);
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
|
||||
@ -767,8 +952,6 @@ _public_ PAM_EXTERN int pam_sm_close_session(
|
||||
int flags,
|
||||
int argc, const char **argv) {
|
||||
|
||||
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
||||
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
|
||||
const void *existing = NULL;
|
||||
const char *id;
|
||||
int r;
|
||||
@ -781,17 +964,15 @@ _public_ PAM_EXTERN int pam_sm_close_session(
|
||||
|
||||
id = pam_getenv(handle, "XDG_SESSION_ID");
|
||||
if (id && !existing) {
|
||||
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
||||
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
|
||||
|
||||
/* Before we go and close the FIFO we need to tell
|
||||
* logind that this is a clean session shutdown, so
|
||||
* that it doesn't just go and slaughter us
|
||||
* immediately after closing the fd */
|
||||
/* Before we go and close the FIFO we need to tell logind that this is a clean session
|
||||
* shutdown, so that it doesn't just go and slaughter us immediately after closing the fd */
|
||||
|
||||
r = sd_bus_open_system(&bus);
|
||||
if (r < 0) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror_safe(r));
|
||||
return PAM_SESSION_ERR;
|
||||
}
|
||||
r = pam_acquire_bus_connection(handle, &bus);
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
|
||||
r = sd_bus_call_method(bus,
|
||||
"org.freedesktop.login1",
|
||||
@ -808,11 +989,9 @@ _public_ PAM_EXTERN int pam_sm_close_session(
|
||||
}
|
||||
}
|
||||
|
||||
/* Note that we are knowingly leaking the FIFO fd here. This
|
||||
* way, logind can watch us die. If we closed it here it would
|
||||
* not have any clue when that is completed. Given that one
|
||||
* cannot really have multiple PAM sessions open from the same
|
||||
* process this means we will leak one FD at max. */
|
||||
/* Note that we are knowingly leaking the FIFO fd here. This way, logind can watch us die. If we
|
||||
* closed it here it would not have any clue when that is completed. Given that one cannot really
|
||||
* have multiple PAM sessions open from the same process this means we will leak one FD at max. */
|
||||
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -19,5 +19,6 @@ global:
|
||||
_nss_systemd_endgrent;
|
||||
_nss_systemd_setgrent;
|
||||
_nss_systemd_getgrent_r;
|
||||
_nss_systemd_initgroups_dyn;
|
||||
local: *;
|
||||
};
|
||||
|
344
src/nss-systemd/userdb-glue.c
Normal file
344
src/nss-systemd/userdb-glue.c
Normal file
@ -0,0 +1,344 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
|
||||
#include "env-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "group-record-nss.h"
|
||||
#include "strv.h"
|
||||
#include "user-record.h"
|
||||
#include "userdb-glue.h"
|
||||
#include "userdb.h"
|
||||
|
||||
UserDBFlags nss_glue_userdb_flags(void) {
|
||||
UserDBFlags flags = USERDB_AVOID_NSS;
|
||||
|
||||
/* Make sure that we don't go in circles when allocating a dynamic UID by checking our own database */
|
||||
if (getenv_bool_secure("SYSTEMD_NSS_DYNAMIC_BYPASS") > 0)
|
||||
flags |= USERDB_AVOID_DYNAMIC_USER;
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
int nss_pack_user_record(
|
||||
UserRecord *hr,
|
||||
struct passwd *pwd,
|
||||
char *buffer,
|
||||
size_t buflen) {
|
||||
|
||||
const char *rn, *hd, *shell;
|
||||
size_t required;
|
||||
|
||||
assert(hr);
|
||||
assert(pwd);
|
||||
|
||||
assert_se(hr->user_name);
|
||||
required = strlen(hr->user_name) + 1;
|
||||
|
||||
assert_se(rn = user_record_real_name(hr));
|
||||
required += strlen(rn) + 1;
|
||||
|
||||
assert_se(hd = user_record_home_directory(hr));
|
||||
required += strlen(hd) + 1;
|
||||
|
||||
assert_se(shell = user_record_shell(hr));
|
||||
required += strlen(shell) + 1;
|
||||
|
||||
if (buflen < required)
|
||||
return -ERANGE;
|
||||
|
||||
*pwd = (struct passwd) {
|
||||
.pw_name = buffer,
|
||||
.pw_uid = hr->uid,
|
||||
.pw_gid = user_record_gid(hr),
|
||||
.pw_passwd = (char*) "x", /* means: see shadow file */
|
||||
};
|
||||
|
||||
assert(buffer);
|
||||
|
||||
pwd->pw_gecos = stpcpy(pwd->pw_name, hr->user_name) + 1;
|
||||
pwd->pw_dir = stpcpy(pwd->pw_gecos, rn) + 1;
|
||||
pwd->pw_shell = stpcpy(pwd->pw_dir, hd) + 1;
|
||||
strcpy(pwd->pw_shell, shell);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
enum nss_status userdb_getpwnam(
|
||||
const char *name,
|
||||
struct passwd *pwd,
|
||||
char *buffer, size_t buflen,
|
||||
int *errnop) {
|
||||
|
||||
_cleanup_(user_record_unrefp) UserRecord *hr = NULL;
|
||||
int r;
|
||||
|
||||
assert(pwd);
|
||||
assert(errnop);
|
||||
|
||||
r = userdb_nss_compat_is_enabled();
|
||||
if (r < 0) {
|
||||
*errnop = -r;
|
||||
return NSS_STATUS_UNAVAIL;
|
||||
}
|
||||
if (!r)
|
||||
return NSS_STATUS_NOTFOUND;
|
||||
|
||||
r = userdb_by_name(name, nss_glue_userdb_flags(), &hr);
|
||||
if (r == -ESRCH)
|
||||
return NSS_STATUS_NOTFOUND;
|
||||
if (r < 0) {
|
||||
*errnop = -r;
|
||||
return NSS_STATUS_UNAVAIL;
|
||||
}
|
||||
|
||||
r = nss_pack_user_record(hr, pwd, buffer, buflen);
|
||||
if (r < 0) {
|
||||
*errnop = -r;
|
||||
return NSS_STATUS_TRYAGAIN;
|
||||
}
|
||||
|
||||
return NSS_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
enum nss_status userdb_getpwuid(
|
||||
uid_t uid,
|
||||
struct passwd *pwd,
|
||||
char *buffer,
|
||||
size_t buflen,
|
||||
int *errnop) {
|
||||
|
||||
_cleanup_(user_record_unrefp) UserRecord *hr = NULL;
|
||||
int r;
|
||||
|
||||
assert(pwd);
|
||||
assert(errnop);
|
||||
|
||||
r = userdb_nss_compat_is_enabled();
|
||||
if (r < 0) {
|
||||
*errnop = -r;
|
||||
return NSS_STATUS_UNAVAIL;
|
||||
}
|
||||
if (!r)
|
||||
return NSS_STATUS_NOTFOUND;
|
||||
|
||||
r = userdb_by_uid(uid, nss_glue_userdb_flags(), &hr);
|
||||
if (r == -ESRCH)
|
||||
return NSS_STATUS_NOTFOUND;
|
||||
if (r < 0) {
|
||||
*errnop = -r;
|
||||
return NSS_STATUS_UNAVAIL;
|
||||
}
|
||||
|
||||
r = nss_pack_user_record(hr, pwd, buffer, buflen);
|
||||
if (r < 0) {
|
||||
*errnop = -r;
|
||||
return NSS_STATUS_TRYAGAIN;
|
||||
}
|
||||
|
||||
return NSS_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
int nss_pack_group_record(
|
||||
GroupRecord *g,
|
||||
char **extra_members,
|
||||
struct group *gr,
|
||||
char *buffer,
|
||||
size_t buflen) {
|
||||
|
||||
char **array = NULL, *p, **m;
|
||||
size_t required, n = 0, i = 0;
|
||||
|
||||
assert(g);
|
||||
assert(gr);
|
||||
|
||||
assert_se(g->group_name);
|
||||
required = strlen(g->group_name) + 1;
|
||||
|
||||
STRV_FOREACH(m, g->members) {
|
||||
required += sizeof(char*); /* space for ptr array entry */
|
||||
required += strlen(*m) + 1;
|
||||
n++;
|
||||
}
|
||||
STRV_FOREACH(m, extra_members) {
|
||||
if (strv_contains(g->members, *m))
|
||||
continue;
|
||||
|
||||
required += sizeof(char*);
|
||||
required += strlen(*m) + 1;
|
||||
n++;
|
||||
}
|
||||
|
||||
required += sizeof(char*); /* trailing NULL in ptr array entry */
|
||||
|
||||
if (buflen < required)
|
||||
return -ERANGE;
|
||||
|
||||
array = (char**) buffer; /* place ptr array at beginning of buffer, under assumption buffer is aligned */
|
||||
p = buffer + sizeof(void*) * (n + 1); /* place member strings right after the ptr array */
|
||||
|
||||
STRV_FOREACH(m, g->members) {
|
||||
array[i++] = p;
|
||||
p = stpcpy(p, *m) + 1;
|
||||
}
|
||||
STRV_FOREACH(m, extra_members) {
|
||||
if (strv_contains(g->members, *m))
|
||||
continue;
|
||||
|
||||
array[i++] = p;
|
||||
p = stpcpy(p, *m) + 1;
|
||||
}
|
||||
|
||||
assert_se(i == n);
|
||||
array[n] = NULL;
|
||||
|
||||
*gr = (struct group) {
|
||||
.gr_name = strcpy(p, g->group_name),
|
||||
.gr_gid = g->gid,
|
||||
.gr_passwd = (char*) "x", /* means: see shadow file */
|
||||
.gr_mem = array,
|
||||
};
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
enum nss_status userdb_getgrnam(
|
||||
const char *name,
|
||||
struct group *gr,
|
||||
char *buffer,
|
||||
size_t buflen,
|
||||
int *errnop) {
|
||||
|
||||
_cleanup_(group_record_unrefp) GroupRecord *g = NULL;
|
||||
_cleanup_strv_free_ char **members = NULL;
|
||||
int r;
|
||||
|
||||
assert(gr);
|
||||
assert(errnop);
|
||||
|
||||
r = userdb_nss_compat_is_enabled();
|
||||
if (r < 0) {
|
||||
*errnop = -r;
|
||||
return NSS_STATUS_UNAVAIL;
|
||||
}
|
||||
if (!r)
|
||||
return NSS_STATUS_NOTFOUND;
|
||||
|
||||
r = groupdb_by_name(name, nss_glue_userdb_flags(), &g);
|
||||
if (r < 0 && r != -ESRCH) {
|
||||
*errnop = -r;
|
||||
return NSS_STATUS_UNAVAIL;
|
||||
}
|
||||
|
||||
r = membershipdb_by_group_strv(name, nss_glue_userdb_flags(), &members);
|
||||
if (r < 0) {
|
||||
*errnop = -r;
|
||||
return NSS_STATUS_UNAVAIL;
|
||||
}
|
||||
|
||||
if (!g) {
|
||||
_cleanup_close_ int lock_fd = -1;
|
||||
|
||||
if (strv_isempty(members))
|
||||
return NSS_STATUS_NOTFOUND;
|
||||
|
||||
/* Grmbl, so we are supposed to extend a group entry, but the group entry itself is not
|
||||
* accessible via non-NSS. Hence let's do what we have to do, and query NSS after all to
|
||||
* acquire it, so that we can extend it (that's because glibc's group merging feature will
|
||||
* merge groups only if both GID and name match and thus we need to have both first). It
|
||||
* sucks behaving recursively likely this, but it's apparently what everybody does. We break
|
||||
* the recursion for ourselves via the userdb_nss_compat_disable() lock. */
|
||||
|
||||
lock_fd = userdb_nss_compat_disable();
|
||||
if (lock_fd < 0 && lock_fd != -EBUSY)
|
||||
return lock_fd;
|
||||
|
||||
r = nss_group_record_by_name(name, &g);
|
||||
if (r == -ESRCH)
|
||||
return NSS_STATUS_NOTFOUND;
|
||||
if (r < 0) {
|
||||
*errnop = -r;
|
||||
return NSS_STATUS_UNAVAIL;
|
||||
}
|
||||
}
|
||||
|
||||
r = nss_pack_group_record(g, members, gr, buffer, buflen);
|
||||
if (r < 0) {
|
||||
*errnop = -r;
|
||||
return NSS_STATUS_TRYAGAIN;
|
||||
}
|
||||
|
||||
return NSS_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
enum nss_status userdb_getgrgid(
|
||||
gid_t gid,
|
||||
struct group *gr,
|
||||
char *buffer,
|
||||
size_t buflen,
|
||||
int *errnop) {
|
||||
|
||||
|
||||
_cleanup_(group_record_unrefp) GroupRecord *g = NULL;
|
||||
_cleanup_strv_free_ char **members = NULL;
|
||||
bool from_nss;
|
||||
int r;
|
||||
|
||||
assert(gr);
|
||||
assert(errnop);
|
||||
|
||||
r = userdb_nss_compat_is_enabled();
|
||||
if (r < 0) {
|
||||
*errnop = -r;
|
||||
return NSS_STATUS_UNAVAIL;
|
||||
}
|
||||
if (r)
|
||||
return NSS_STATUS_NOTFOUND;
|
||||
|
||||
r = groupdb_by_gid(gid, nss_glue_userdb_flags(), &g);
|
||||
if (r < 0 && r != -ESRCH) {
|
||||
*errnop = -r;
|
||||
return NSS_STATUS_UNAVAIL;
|
||||
}
|
||||
|
||||
if (!g) {
|
||||
_cleanup_close_ int lock_fd = -1;
|
||||
|
||||
/* So, quite possibly we have to extend an existing group record with additional members. But
|
||||
* to do this we need to know the group name first. The group didn't exist via non-NSS
|
||||
* queries though, hence let's try to acquire it here recursively via NSS. */
|
||||
|
||||
lock_fd = userdb_nss_compat_disable();
|
||||
if (lock_fd < 0 && lock_fd != -EBUSY)
|
||||
return lock_fd;
|
||||
|
||||
r = nss_group_record_by_gid(gid, &g);
|
||||
if (r == -ESRCH)
|
||||
return NSS_STATUS_NOTFOUND;
|
||||
|
||||
if (r < 0) {
|
||||
*errnop = -r;
|
||||
return NSS_STATUS_UNAVAIL;
|
||||
}
|
||||
|
||||
from_nss = true;
|
||||
} else
|
||||
from_nss = false;
|
||||
|
||||
r = membershipdb_by_group_strv(g->group_name, nss_glue_userdb_flags(), &members);
|
||||
if (r < 0) {
|
||||
*errnop = -r;
|
||||
return NSS_STATUS_UNAVAIL;
|
||||
}
|
||||
|
||||
/* If we acquired the record via NSS then there's no reason to respond unless we have to agument the
|
||||
* list of members of the group */
|
||||
if (from_nss && strv_isempty(members))
|
||||
return NSS_STATUS_NOTFOUND;
|
||||
|
||||
r = nss_pack_group_record(g, members, gr, buffer, buflen);
|
||||
if (r < 0) {
|
||||
*errnop = -r;
|
||||
return NSS_STATUS_TRYAGAIN;
|
||||
}
|
||||
|
||||
return NSS_STATUS_SUCCESS;
|
||||
}
|
20
src/nss-systemd/userdb-glue.h
Normal file
20
src/nss-systemd/userdb-glue.h
Normal file
@ -0,0 +1,20 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#include <nss.h>
|
||||
#include <pwd.h>
|
||||
#include <grp.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "userdb.h"
|
||||
|
||||
UserDBFlags nss_glue_userdb_flags(void);
|
||||
|
||||
int nss_pack_user_record(UserRecord *hr, struct passwd *pwd, char *buffer, size_t buflen);
|
||||
int nss_pack_group_record(GroupRecord *g, char **extra_members, struct group *gr, char *buffer, size_t buflen);
|
||||
|
||||
enum nss_status userdb_getpwnam(const char *name, struct passwd *pwd, char *buffer, size_t buflen, int *errnop);
|
||||
enum nss_status userdb_getpwuid(uid_t uid, struct passwd *pwd, char *buffer, size_t buflen, int *errnop);
|
||||
|
||||
enum nss_status userdb_getgrnam(const char *name, struct group *gr, char *buffer, size_t buflen, int *errnop);
|
||||
enum nss_status userdb_getgrgid(gid_t gid, struct group *gr, char *buffer, size_t buflen, int *errnop);
|
203
src/shared/group-record-nss.c
Normal file
203
src/shared/group-record-nss.c
Normal file
@ -0,0 +1,203 @@
|
||||
/* 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, GroupRecord **ret) {
|
||||
_cleanup_free_ char *buf = NULL, *sbuf = NULL;
|
||||
struct group grp, *result;
|
||||
bool incomplete = false;
|
||||
size_t buflen = 4096;
|
||||
struct sgrp sgrp;
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
r = nss_group_to_group_record(result, r >= 0 ? &sgrp : NULL, ret);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
(*ret)->incomplete = incomplete;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int nss_group_record_by_gid(gid_t gid, GroupRecord **ret) {
|
||||
_cleanup_free_ char *buf = NULL, *sbuf = NULL;
|
||||
struct group grp, *result;
|
||||
bool incomplete = false;
|
||||
size_t buflen = 4096;
|
||||
struct sgrp sgrp;
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
r = nss_group_to_group_record(result, r >= 0 ? &sgrp : NULL, ret);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
(*ret)->incomplete = incomplete;
|
||||
return 0;
|
||||
}
|
15
src/shared/group-record-nss.h
Normal file
15
src/shared/group-record-nss.h
Normal file
@ -0,0 +1,15 @@
|
||||
/* 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, GroupRecord **ret);
|
||||
int nss_group_record_by_gid(gid_t gid, GroupRecord **ret);
|
76
src/shared/group-record-show.c
Normal file
76
src/shared/group-record-show.c
Normal file
@ -0,0 +1,76 @@
|
||||
/* 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 (!strv_isempty(gr->hashed_password))
|
||||
printf(" Passwords: %zu\n", strv_length(gr->hashed_password));
|
||||
|
||||
if (gr->service)
|
||||
printf(" Service: %s\n", gr->service);
|
||||
}
|
6
src/shared/group-record-show.h
Normal file
6
src/shared/group-record-show.h
Normal file
@ -0,0 +1,6 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#include "group-record.h"
|
||||
|
||||
void group_record_show(GroupRecord *gr, bool show_full_user_info);
|
346
src/shared/group-record.c
Normal file
346
src/shared/group-record.c
Normal file
@ -0,0 +1,346 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
|
||||
#include "group-record.h"
|
||||
#include "strv.h"
|
||||
#include "user-util.h"
|
||||
|
||||
GroupRecord* group_record_new(void) {
|
||||
GroupRecord *h;
|
||||
|
||||
h = new(GroupRecord, 1);
|
||||
if (!h)
|
||||
return NULL;
|
||||
|
||||
*h = (GroupRecord) {
|
||||
.n_ref = 1,
|
||||
.disposition = _USER_DISPOSITION_INVALID,
|
||||
.last_change_usec = UINT64_MAX,
|
||||
.gid = GID_INVALID,
|
||||
};
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
static GroupRecord *group_record_free(GroupRecord *g) {
|
||||
if (!g)
|
||||
return NULL;
|
||||
|
||||
free(g->group_name);
|
||||
free(g->realm);
|
||||
free(g->group_name_and_realm_auto);
|
||||
|
||||
strv_free(g->members);
|
||||
free(g->service);
|
||||
strv_free(g->administrators);
|
||||
strv_free_erase(g->hashed_password);
|
||||
|
||||
json_variant_unref(g->json);
|
||||
|
||||
return mfree(g);
|
||||
}
|
||||
|
||||
DEFINE_TRIVIAL_REF_UNREF_FUNC(GroupRecord, group_record, group_record_free);
|
||||
|
||||
static int dispatch_privileged(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
|
||||
|
||||
static const JsonDispatch privileged_dispatch_table[] = {
|
||||
{ "hashedPassword", _JSON_VARIANT_TYPE_INVALID, json_dispatch_strv, offsetof(GroupRecord, hashed_password), JSON_SAFE },
|
||||
{},
|
||||
};
|
||||
|
||||
return json_dispatch(variant, privileged_dispatch_table, NULL, flags, userdata);
|
||||
}
|
||||
|
||||
static int dispatch_binding(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
|
||||
|
||||
static const JsonDispatch binding_dispatch_table[] = {
|
||||
{ "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(GroupRecord, gid), 0 },
|
||||
{},
|
||||
};
|
||||
|
||||
char smid[SD_ID128_STRING_MAX];
|
||||
JsonVariant *m;
|
||||
sd_id128_t mid;
|
||||
int r;
|
||||
|
||||
if (!variant)
|
||||
return 0;
|
||||
|
||||
if (!json_variant_is_object(variant))
|
||||
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an object.", strna(name));
|
||||
|
||||
r = sd_id128_get_machine(&mid);
|
||||
if (r < 0)
|
||||
return json_log(variant, flags, r, "Failed to determine machine ID: %m");
|
||||
|
||||
m = json_variant_by_key(variant, sd_id128_to_string(mid, smid));
|
||||
if (!m)
|
||||
return 0;
|
||||
|
||||
return json_dispatch(m, binding_dispatch_table, NULL, flags, userdata);
|
||||
}
|
||||
|
||||
static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
|
||||
|
||||
static const JsonDispatch per_machine_dispatch_table[] = {
|
||||
{ "matchMachineId", _JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 },
|
||||
{ "matchHostname", _JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 },
|
||||
{ "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(GroupRecord, gid), 0 },
|
||||
{ "members", JSON_VARIANT_ARRAY, json_dispatch_user_group_list, offsetof(GroupRecord, members), 0 },
|
||||
{ "administrators", JSON_VARIANT_ARRAY, json_dispatch_user_group_list, offsetof(GroupRecord, administrators), 0 },
|
||||
{},
|
||||
};
|
||||
|
||||
JsonVariant *e;
|
||||
int r;
|
||||
|
||||
if (!variant)
|
||||
return 0;
|
||||
|
||||
if (!json_variant_is_array(variant))
|
||||
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name));
|
||||
|
||||
JSON_VARIANT_ARRAY_FOREACH(e, variant) {
|
||||
bool matching = false;
|
||||
JsonVariant *m;
|
||||
|
||||
if (!json_variant_is_object(e))
|
||||
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of objects.", strna(name));
|
||||
|
||||
m = json_variant_by_key(e, "matchMachineId");
|
||||
if (m) {
|
||||
r = per_machine_id_match(m, flags);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
matching = r > 0;
|
||||
}
|
||||
|
||||
if (!matching) {
|
||||
m = json_variant_by_key(e, "matchHostname");
|
||||
if (m) {
|
||||
r = per_machine_hostname_match(m, flags);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
matching = r > 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!matching)
|
||||
continue;
|
||||
|
||||
r = json_dispatch(e, per_machine_dispatch_table, NULL, flags, userdata);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dispatch_status(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
|
||||
|
||||
static const JsonDispatch status_dispatch_table[] = {
|
||||
{ "service", JSON_VARIANT_STRING, json_dispatch_string, offsetof(GroupRecord, service), JSON_SAFE },
|
||||
{},
|
||||
};
|
||||
|
||||
char smid[SD_ID128_STRING_MAX];
|
||||
JsonVariant *m;
|
||||
sd_id128_t mid;
|
||||
int r;
|
||||
|
||||
if (!variant)
|
||||
return 0;
|
||||
|
||||
if (!json_variant_is_object(variant))
|
||||
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an object.", strna(name));
|
||||
|
||||
r = sd_id128_get_machine(&mid);
|
||||
if (r < 0)
|
||||
return json_log(variant, flags, r, "Failed to determine machine ID: %m");
|
||||
|
||||
m = json_variant_by_key(variant, sd_id128_to_string(mid, smid));
|
||||
if (!m)
|
||||
return 0;
|
||||
|
||||
return json_dispatch(m, status_dispatch_table, NULL, flags, userdata);
|
||||
}
|
||||
|
||||
static int group_record_augment(GroupRecord *h, JsonDispatchFlags json_flags) {
|
||||
assert(h);
|
||||
|
||||
if (!FLAGS_SET(h->mask, USER_RECORD_REGULAR))
|
||||
return 0;
|
||||
|
||||
assert(h->group_name);
|
||||
|
||||
if (!h->group_name_and_realm_auto && h->realm) {
|
||||
h->group_name_and_realm_auto = strjoin(h->group_name, "@", h->realm);
|
||||
if (!h->group_name_and_realm_auto)
|
||||
return json_log_oom(h->json, json_flags);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int group_record_load(
|
||||
GroupRecord *h,
|
||||
JsonVariant *v,
|
||||
UserRecordLoadFlags load_flags) {
|
||||
|
||||
static const JsonDispatch group_dispatch_table[] = {
|
||||
{ "groupName", JSON_VARIANT_STRING, json_dispatch_user_group_name, offsetof(GroupRecord, group_name), 0 },
|
||||
{ "realm", JSON_VARIANT_STRING, json_dispatch_realm, offsetof(GroupRecord, realm), 0 },
|
||||
{ "disposition", JSON_VARIANT_STRING, json_dispatch_user_disposition, offsetof(GroupRecord, disposition), 0 },
|
||||
{ "service", JSON_VARIANT_STRING, json_dispatch_string, offsetof(GroupRecord, service), JSON_SAFE },
|
||||
{ "lastChangeUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(GroupRecord, last_change_usec), 0 },
|
||||
{ "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(GroupRecord, gid), 0 },
|
||||
{ "members", JSON_VARIANT_ARRAY, json_dispatch_user_group_list, offsetof(GroupRecord, members), 0 },
|
||||
{ "administrators", JSON_VARIANT_ARRAY, json_dispatch_user_group_list, offsetof(GroupRecord, administrators), 0 },
|
||||
|
||||
{ "privileged", JSON_VARIANT_OBJECT, dispatch_privileged, 0, 0 },
|
||||
|
||||
/* Not defined for now, for groups, but let's at least generate sensible errors about it */
|
||||
{ "secret", JSON_VARIANT_OBJECT, json_dispatch_unsupported, 0, 0 },
|
||||
|
||||
/* Ignore the perMachine, binding and status stuff here, and process it later, so that it overrides whatever is set above */
|
||||
{ "perMachine", JSON_VARIANT_ARRAY, NULL, 0, 0 },
|
||||
{ "binding", JSON_VARIANT_OBJECT, NULL, 0, 0 },
|
||||
{ "status", JSON_VARIANT_OBJECT, NULL, 0, 0 },
|
||||
|
||||
/* Ignore 'signature', we check it with explicit accessors instead */
|
||||
{ "signature", JSON_VARIANT_ARRAY, NULL, 0, 0 },
|
||||
{},
|
||||
};
|
||||
|
||||
JsonDispatchFlags json_flags = USER_RECORD_LOAD_FLAGS_TO_JSON_DISPATCH_FLAGS(load_flags);
|
||||
int r;
|
||||
|
||||
assert(h);
|
||||
assert(!h->json);
|
||||
|
||||
/* Note that this call will leave a half-initialized record around on failure! */
|
||||
|
||||
if ((USER_RECORD_REQUIRE_MASK(load_flags) & (USER_RECORD_SECRET|USER_RECORD_PRIVILEGED)))
|
||||
return json_log(v, json_flags, SYNTHETIC_ERRNO(EINVAL), "Secret and privileged section currently not available for groups, refusing.");
|
||||
|
||||
r = user_group_record_mangle(v, load_flags, &h->json, &h->mask);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = json_dispatch(h->json, group_dispatch_table, NULL, json_flags, h);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* During the parsing operation above we ignored the 'perMachine', 'binding' and 'status' fields, since we want
|
||||
* them to override the global options. Let's process them now. */
|
||||
|
||||
r = dispatch_per_machine("perMachine", json_variant_by_key(h->json, "perMachine"), json_flags, h);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = dispatch_binding("binding", json_variant_by_key(h->json, "binding"), json_flags, h);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = dispatch_status("status", json_variant_by_key(h->json, "status"), json_flags, h);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (FLAGS_SET(h->mask, USER_RECORD_REGULAR) && !h->group_name)
|
||||
return json_log(h->json, json_flags, SYNTHETIC_ERRNO(EINVAL), "Group name field missing, refusing.");
|
||||
|
||||
r = group_record_augment(h, json_flags);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int group_record_build(GroupRecord **ret, ...) {
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
|
||||
_cleanup_(group_record_unrefp) GroupRecord *g = NULL;
|
||||
va_list ap;
|
||||
int r;
|
||||
|
||||
assert(ret);
|
||||
|
||||
va_start(ap, ret);
|
||||
r = json_buildv(&v, ap);
|
||||
va_end(ap);
|
||||
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
g = group_record_new();
|
||||
if (!g)
|
||||
return -ENOMEM;
|
||||
|
||||
r = group_record_load(g, v, USER_RECORD_LOAD_FULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
*ret = TAKE_PTR(g);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *group_record_group_name_and_realm(GroupRecord *h) {
|
||||
assert(h);
|
||||
|
||||
/* Return the pre-initialized joined string if it is defined */
|
||||
if (h->group_name_and_realm_auto)
|
||||
return h->group_name_and_realm_auto;
|
||||
|
||||
/* If it's not defined then we cannot have a realm */
|
||||
assert(!h->realm);
|
||||
return h->group_name;
|
||||
}
|
||||
|
||||
UserDisposition group_record_disposition(GroupRecord *h) {
|
||||
assert(h);
|
||||
|
||||
if (h->disposition >= 0)
|
||||
return h->disposition;
|
||||
|
||||
/* If not declared, derive from GID */
|
||||
|
||||
if (!gid_is_valid(h->gid))
|
||||
return _USER_DISPOSITION_INVALID;
|
||||
|
||||
if (h->gid == 0 || h->gid == GID_NOBODY)
|
||||
return USER_INTRINSIC;
|
||||
|
||||
if (gid_is_system(h->gid))
|
||||
return USER_SYSTEM;
|
||||
|
||||
if (gid_is_dynamic(h->gid))
|
||||
return USER_DYNAMIC;
|
||||
|
||||
if (gid_is_container(h->gid))
|
||||
return USER_CONTAINER;
|
||||
|
||||
if (h->gid > INT32_MAX)
|
||||
return USER_RESERVED;
|
||||
|
||||
return USER_REGULAR;
|
||||
}
|
||||
|
||||
int group_record_clone(GroupRecord *h, UserRecordLoadFlags flags, GroupRecord **ret) {
|
||||
_cleanup_(group_record_unrefp) GroupRecord *c = NULL;
|
||||
int r;
|
||||
|
||||
assert(h);
|
||||
assert(ret);
|
||||
|
||||
c = group_record_new();
|
||||
if (!c)
|
||||
return -ENOMEM;
|
||||
|
||||
r = group_record_load(c, h->json, flags);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
*ret = TAKE_PTR(c);
|
||||
return 0;
|
||||
}
|
44
src/shared/group-record.h
Normal file
44
src/shared/group-record.h
Normal file
@ -0,0 +1,44 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#include "json.h"
|
||||
#include "user-record.h"
|
||||
|
||||
typedef struct GroupRecord {
|
||||
unsigned n_ref;
|
||||
UserRecordMask mask;
|
||||
bool incomplete;
|
||||
|
||||
char *group_name;
|
||||
char *realm;
|
||||
char *group_name_and_realm_auto;
|
||||
|
||||
UserDisposition disposition;
|
||||
uint64_t last_change_usec;
|
||||
|
||||
gid_t gid;
|
||||
|
||||
char **members;
|
||||
|
||||
char *service;
|
||||
|
||||
/* The following exist mostly so that we can cover the full /etc/gshadow set of fields, we currently
|
||||
* do not actually make use of these */
|
||||
char **administrators; /* maps to 'struct sgrp' .sg_adm field */
|
||||
char **hashed_password; /* maps to 'struct sgrp' .sg_passwd field */
|
||||
|
||||
JsonVariant *json;
|
||||
} GroupRecord;
|
||||
|
||||
GroupRecord* group_record_new(void);
|
||||
GroupRecord* group_record_ref(GroupRecord *g);
|
||||
GroupRecord* group_record_unref(GroupRecord *g);
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(GroupRecord*, group_record_unref);
|
||||
|
||||
int group_record_load(GroupRecord *h, JsonVariant *v, UserRecordLoadFlags flags);
|
||||
int group_record_build(GroupRecord **ret, ...);
|
||||
int group_record_clone(GroupRecord *g, UserRecordLoadFlags flags, GroupRecord **ret);
|
||||
|
||||
const char *group_record_group_name_and_realm(GroupRecord *h);
|
||||
UserDisposition group_record_disposition(GroupRecord *h);
|
86
src/shared/libcrypt-util.c
Normal file
86
src/shared/libcrypt-util.c
Normal file
@ -0,0 +1,86 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "libcrypt-util.h"
|
||||
#include "log.h"
|
||||
#include "macro.h"
|
||||
#include "missing_stdlib.h"
|
||||
#include "random-util.h"
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
|
||||
int make_salt(char **ret) {
|
||||
|
||||
#ifdef XCRYPT_VERSION_MAJOR
|
||||
const char *e;
|
||||
char *salt;
|
||||
|
||||
/* If we have libxcrypt we default to the "preferred method" (i.e. usually yescrypt), and generate it
|
||||
* with crypt_gensalt_ra(). */
|
||||
|
||||
e = secure_getenv("SYSTEMD_CRYPT_PREFIX");
|
||||
if (!e)
|
||||
e = crypt_preferred_method();
|
||||
|
||||
log_debug("Generating salt for hash prefix: %s", e);
|
||||
|
||||
salt = crypt_gensalt_ra(e, 0, NULL, 0);
|
||||
if (!salt)
|
||||
return -errno;
|
||||
|
||||
*ret = salt;
|
||||
return 0;
|
||||
#else
|
||||
/* If libxcrypt is not used, we use SHA512 and generate the salt on our own since crypt_gensalt_ra()
|
||||
* is not available. */
|
||||
|
||||
static const char table[] =
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"0123456789"
|
||||
"./";
|
||||
|
||||
uint8_t raw[16];
|
||||
char *salt, *j;
|
||||
size_t i;
|
||||
int r;
|
||||
|
||||
/* This is a bit like crypt_gensalt_ra(), but doesn't require libcrypt, and doesn't do anything but
|
||||
* SHA512, i.e. is legacy-free and minimizes our deps. */
|
||||
|
||||
assert_cc(sizeof(table) == 64U + 1U);
|
||||
|
||||
/* Insist on the best randomness by setting RANDOM_BLOCK, this is about keeping passwords secret after all. */
|
||||
r = genuine_random_bytes(raw, sizeof(raw), RANDOM_BLOCK);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
salt = new(char, 3+sizeof(raw)+1+1);
|
||||
if (!salt)
|
||||
return -ENOMEM;
|
||||
|
||||
/* We only bother with SHA512 hashed passwords, the rest is legacy, and we don't do legacy. */
|
||||
j = stpcpy(salt, "$6$");
|
||||
for (i = 0; i < sizeof(raw); i++)
|
||||
j[i] = table[raw[i] & 63];
|
||||
j[i++] = '$';
|
||||
j[i] = 0;
|
||||
|
||||
*ret = salt;
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool hashed_password_valid(const char *s) {
|
||||
|
||||
/* Returns true if the specified string is a 'valid' hashed UNIX password, i.e. if starts with '$' or
|
||||
* with '!$' (the latter being a valid, yet locked password). */
|
||||
|
||||
if (isempty(s))
|
||||
return false;
|
||||
|
||||
return STARTSWITH_SET(s, "$", "!$");
|
||||
}
|
22
src/shared/libcrypt-util.h
Normal file
22
src/shared/libcrypt-util.h
Normal file
@ -0,0 +1,22 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#if HAVE_CRYPT_H
|
||||
/* libxcrypt is a replacement for glibc's libcrypt, and libcrypt might be
|
||||
* removed from glibc at some point. As part of the removal, defines for
|
||||
* crypt(3) are dropped from unistd.h, and we must include crypt.h instead.
|
||||
*
|
||||
* Newer versions of glibc (v2.0+) already ship crypt.h with a definition
|
||||
* of crypt(3) as well, so we simply include it if it is present. MariaDB,
|
||||
* MySQL, PostgreSQL, Perl and some other wide-spread packages do it the
|
||||
* same way since ages without any problems.
|
||||
*/
|
||||
#include <crypt.h>
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int make_salt(char **ret);
|
||||
|
||||
bool hashed_password_valid(const char *s);
|
@ -86,6 +86,12 @@ shared_sources = files('''
|
||||
generator.c
|
||||
generator.h
|
||||
gpt.h
|
||||
group-record-nss.c
|
||||
group-record-nss.h
|
||||
group-record-show.c
|
||||
group-record-show.h
|
||||
group-record.c
|
||||
group-record.h
|
||||
id128-print.c
|
||||
id128-print.h
|
||||
ima-util.c
|
||||
@ -106,6 +112,8 @@ shared_sources = files('''
|
||||
json-internal.h
|
||||
json.c
|
||||
json.h
|
||||
libcrypt-util.c
|
||||
libcrypt-util.h
|
||||
libmount-util.h
|
||||
linux/auto_dev-ioctl.h
|
||||
linux/bpf.h
|
||||
@ -187,6 +195,14 @@ shared_sources = files('''
|
||||
uid-range.h
|
||||
unit-file.c
|
||||
unit-file.h
|
||||
user-record-nss.c
|
||||
user-record-nss.h
|
||||
user-record-show.c
|
||||
user-record-show.h
|
||||
user-record.c
|
||||
user-record.h
|
||||
userdb.c
|
||||
userdb.h
|
||||
utmp-wtmp.h
|
||||
varlink.c
|
||||
varlink.h
|
||||
@ -233,6 +249,13 @@ if conf.get('HAVE_KMOD') == 1
|
||||
shared_sources += files('module-util.c')
|
||||
endif
|
||||
|
||||
if conf.get('HAVE_PAM') == 1
|
||||
shared_sources += files('''
|
||||
pam-util.c
|
||||
pam-util.h
|
||||
'''.split())
|
||||
endif
|
||||
|
||||
generate_ip_protocol_list = find_program('generate-ip-protocol-list.sh')
|
||||
ip_protocol_list_txt = custom_target(
|
||||
'ip-protocol-list.txt',
|
||||
@ -279,6 +302,7 @@ libshared_deps = [threads,
|
||||
libacl,
|
||||
libblkid,
|
||||
libcap,
|
||||
libcrypt,
|
||||
libcryptsetup,
|
||||
libgcrypt,
|
||||
libidn,
|
||||
@ -288,6 +312,7 @@ libshared_deps = [threads,
|
||||
libmount,
|
||||
libopenssl,
|
||||
libp11kit,
|
||||
libpam,
|
||||
librt,
|
||||
libseccomp,
|
||||
libselinux,
|
||||
|
81
src/shared/pam-util.c
Normal file
81
src/shared/pam-util.c
Normal file
@ -0,0 +1,81 @@
|
||||
#include <security/pam_ext.h>
|
||||
#include <syslog.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "errno-util.h"
|
||||
#include "macro.h"
|
||||
#include "pam-util.h"
|
||||
|
||||
int pam_log_oom(pam_handle_t *handle) {
|
||||
/* This is like log_oom(), but uses PAM logging */
|
||||
pam_syslog(handle, LOG_ERR, "Out of memory.");
|
||||
return PAM_BUF_ERR;
|
||||
}
|
||||
|
||||
int pam_bus_log_create_error(pam_handle_t *handle, int r) {
|
||||
/* This is like bus_log_create_error(), but uses PAM logging */
|
||||
pam_syslog(handle, LOG_ERR, "Failed to create bus message: %s", strerror_safe(r));
|
||||
return PAM_BUF_ERR;
|
||||
}
|
||||
|
||||
int pam_bus_log_parse_error(pam_handle_t *handle, int r) {
|
||||
/* This is like bus_log_parse_error(), but uses PAM logging */
|
||||
pam_syslog(handle, LOG_ERR, "Failed to parse bus message: %s", strerror_safe(r));
|
||||
return PAM_BUF_ERR;
|
||||
}
|
||||
|
||||
static void cleanup_system_bus(pam_handle_t *handle, void *data, int error_status) {
|
||||
sd_bus_flush_close_unref(data);
|
||||
}
|
||||
|
||||
int pam_acquire_bus_connection(pam_handle_t *handle, sd_bus **ret) {
|
||||
_cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
|
||||
int r;
|
||||
|
||||
assert(handle);
|
||||
assert(ret);
|
||||
|
||||
/* We cache the bus connection so that we can share it between the session and the authentication hooks */
|
||||
r = pam_get_data(handle, "systemd-system-bus", (const void**) &bus);
|
||||
if (r == PAM_SUCCESS && bus) {
|
||||
*ret = sd_bus_ref(TAKE_PTR(bus)); /* Increase the reference counter, so that the PAM data stays valid */
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA)) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to get bus connection: %s", pam_strerror(handle, r));
|
||||
return r;
|
||||
}
|
||||
|
||||
r = sd_bus_open_system(&bus);
|
||||
if (r < 0) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror_safe(r));
|
||||
return PAM_SERVICE_ERR;
|
||||
}
|
||||
|
||||
r = pam_set_data(handle, "systemd-system-bus", bus, cleanup_system_bus);
|
||||
if (r != PAM_SUCCESS) {
|
||||
pam_syslog(handle, LOG_ERR, "Failed to set PAM bus data: %s", pam_strerror(handle, r));
|
||||
return r;
|
||||
}
|
||||
|
||||
sd_bus_ref(bus);
|
||||
*ret = TAKE_PTR(bus);
|
||||
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
|
||||
int pam_release_bus_connection(pam_handle_t *handle) {
|
||||
int r;
|
||||
|
||||
r = pam_set_data(handle, "systemd-system-bus", NULL, NULL);
|
||||
if (r != PAM_SUCCESS)
|
||||
pam_syslog(handle, LOG_ERR, "Failed to release PAM user record data: %s", pam_strerror(handle, r));
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
void pam_cleanup_free(pam_handle_t *handle, void *data, int error_status) {
|
||||
/* A generic destructor for pam_set_data() that just frees the specified data */
|
||||
free(data);
|
||||
}
|
15
src/shared/pam-util.h
Normal file
15
src/shared/pam-util.h
Normal file
@ -0,0 +1,15 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#include <security/pam_modules.h>
|
||||
|
||||
#include "sd-bus.h"
|
||||
|
||||
int pam_log_oom(pam_handle_t *handle);
|
||||
int pam_bus_log_create_error(pam_handle_t *handle, int r);
|
||||
int pam_bus_log_parse_error(pam_handle_t *handle, int r);
|
||||
|
||||
int pam_acquire_bus_connection(pam_handle_t *handle, sd_bus **ret);
|
||||
int pam_release_bus_connection(pam_handle_t *handle);
|
||||
|
||||
void pam_cleanup_free(pam_handle_t *handle, void *data, int error_status);
|
288
src/shared/user-record-nss.c
Normal file
288
src/shared/user-record-nss.c
Normal file
@ -0,0 +1,288 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
|
||||
#include "errno-util.h"
|
||||
#include "format-util.h"
|
||||
#include "libcrypt-util.h"
|
||||
#include "strv.h"
|
||||
#include "user-record-nss.h"
|
||||
|
||||
int nss_passwd_to_user_record(
|
||||
const struct passwd *pwd,
|
||||
const struct spwd *spwd,
|
||||
UserRecord **ret) {
|
||||
|
||||
_cleanup_(user_record_unrefp) UserRecord *hr = NULL;
|
||||
int r;
|
||||
|
||||
assert(pwd);
|
||||
assert(ret);
|
||||
|
||||
if (isempty(pwd->pw_name))
|
||||
return -EINVAL;
|
||||
|
||||
if (spwd && !streq_ptr(spwd->sp_namp, pwd->pw_name))
|
||||
return -EINVAL;
|
||||
|
||||
hr = user_record_new();
|
||||
if (!hr)
|
||||
return -ENOMEM;
|
||||
|
||||
r = free_and_strdup(&hr->user_name, pwd->pw_name);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (isempty(pwd->pw_gecos) || streq_ptr(pwd->pw_gecos, hr->user_name))
|
||||
hr->real_name = mfree(hr->real_name);
|
||||
else {
|
||||
r = free_and_strdup(&hr->real_name, pwd->pw_gecos);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (isempty(pwd->pw_dir))
|
||||
hr->home_directory = mfree(hr->home_directory);
|
||||
else {
|
||||
r = free_and_strdup(&hr->home_directory, pwd->pw_dir);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (isempty(pwd->pw_shell))
|
||||
hr->shell = mfree(hr->shell);
|
||||
else {
|
||||
r = free_and_strdup(&hr->shell, pwd->pw_shell);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
hr->uid = pwd->pw_uid;
|
||||
hr->gid = pwd->pw_gid;
|
||||
|
||||
if (spwd) {
|
||||
if (hashed_password_valid(spwd->sp_pwdp)) {
|
||||
strv_free_erase(hr->hashed_password);
|
||||
hr->hashed_password = strv_new(spwd->sp_pwdp);
|
||||
if (!hr->hashed_password)
|
||||
return -ENOMEM;
|
||||
} else
|
||||
hr->hashed_password = strv_free_erase(hr->hashed_password);
|
||||
|
||||
/* shadow-utils suggests using "chage -E 0" (or -E 1, depending on which man page you check)
|
||||
* for locking a whole account, hence check for that. Note that it also defines a way to lock
|
||||
* just a password instead of the whole account, but that's mostly pointless in times of
|
||||
* password-less authorization, hence let's not bother. */
|
||||
|
||||
if (spwd->sp_expire >= 0)
|
||||
hr->locked = spwd->sp_expire <= 1;
|
||||
else
|
||||
hr->locked = -1;
|
||||
|
||||
if (spwd->sp_expire > 1 && (uint64_t) spwd->sp_expire < (UINT64_MAX-1)/USEC_PER_DAY)
|
||||
hr->not_after_usec = spwd->sp_expire * USEC_PER_DAY;
|
||||
else
|
||||
hr->not_after_usec = UINT64_MAX;
|
||||
|
||||
if (spwd->sp_lstchg >= 0)
|
||||
hr->password_change_now = spwd->sp_lstchg == 0;
|
||||
else
|
||||
hr->password_change_now = -1;
|
||||
|
||||
if (spwd->sp_lstchg > 0 && (uint64_t) spwd->sp_lstchg <= (UINT64_MAX-1)/USEC_PER_DAY)
|
||||
hr->last_password_change_usec = spwd->sp_lstchg * USEC_PER_DAY;
|
||||
else
|
||||
hr->last_password_change_usec = UINT64_MAX;
|
||||
|
||||
if (spwd->sp_min > 0 && (uint64_t) spwd->sp_min <= (UINT64_MAX-1)/USEC_PER_DAY)
|
||||
hr->password_change_min_usec = spwd->sp_min * USEC_PER_DAY;
|
||||
else
|
||||
hr->password_change_min_usec = UINT64_MAX;
|
||||
|
||||
if (spwd->sp_max > 0 && (uint64_t) spwd->sp_max <= (UINT64_MAX-1)/USEC_PER_DAY)
|
||||
hr->password_change_max_usec = spwd->sp_max * USEC_PER_DAY;
|
||||
else
|
||||
hr->password_change_max_usec = UINT64_MAX;
|
||||
|
||||
if (spwd->sp_warn > 0 && (uint64_t) spwd->sp_warn <= (UINT64_MAX-1)/USEC_PER_DAY)
|
||||
hr->password_change_warn_usec = spwd->sp_warn * USEC_PER_DAY;
|
||||
else
|
||||
hr->password_change_warn_usec = UINT64_MAX;
|
||||
|
||||
if (spwd->sp_inact > 0 && (uint64_t) spwd->sp_inact <= (UINT64_MAX-1)/USEC_PER_DAY)
|
||||
hr->password_change_inactive_usec = spwd->sp_inact * USEC_PER_DAY;
|
||||
else
|
||||
hr->password_change_inactive_usec = UINT64_MAX;
|
||||
} else {
|
||||
hr->hashed_password = strv_free_erase(hr->hashed_password);
|
||||
hr->locked = -1;
|
||||
hr->not_after_usec = UINT64_MAX;
|
||||
hr->password_change_now = -1,
|
||||
hr->last_password_change_usec = UINT64_MAX;
|
||||
hr->password_change_min_usec = UINT64_MAX;
|
||||
hr->password_change_max_usec = UINT64_MAX;
|
||||
hr->password_change_warn_usec = UINT64_MAX;
|
||||
hr->password_change_inactive_usec = UINT64_MAX;
|
||||
}
|
||||
|
||||
hr->json = json_variant_unref(hr->json);
|
||||
r = json_build(&hr->json, JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(hr->user_name)),
|
||||
JSON_BUILD_PAIR("uid", JSON_BUILD_UNSIGNED(hr->uid)),
|
||||
JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(hr->gid)),
|
||||
JSON_BUILD_PAIR_CONDITION(hr->real_name, "realName", JSON_BUILD_STRING(hr->real_name)),
|
||||
JSON_BUILD_PAIR_CONDITION(hr->home_directory, "homeDirectory", JSON_BUILD_STRING(hr->home_directory)),
|
||||
JSON_BUILD_PAIR_CONDITION(hr->shell, "shell", JSON_BUILD_STRING(hr->shell)),
|
||||
JSON_BUILD_PAIR_CONDITION(!strv_isempty(hr->hashed_password), "privileged", JSON_BUILD_OBJECT(JSON_BUILD_PAIR("hashedPassword", JSON_BUILD_STRV(hr->hashed_password)))),
|
||||
JSON_BUILD_PAIR_CONDITION(hr->locked >= 0, "locked", JSON_BUILD_BOOLEAN(hr->locked)),
|
||||
JSON_BUILD_PAIR_CONDITION(hr->not_after_usec != UINT64_MAX, "notAfterUSec", JSON_BUILD_UNSIGNED(hr->not_after_usec)),
|
||||
JSON_BUILD_PAIR_CONDITION(hr->password_change_now >= 0, "passwordChangeNow", JSON_BUILD_BOOLEAN(hr->password_change_now)),
|
||||
JSON_BUILD_PAIR_CONDITION(hr->last_password_change_usec != UINT64_MAX, "lastPasswordChangeUSec", JSON_BUILD_UNSIGNED(hr->last_password_change_usec)),
|
||||
JSON_BUILD_PAIR_CONDITION(hr->password_change_min_usec != UINT64_MAX, "passwordChangeMinUSec", JSON_BUILD_UNSIGNED(hr->password_change_min_usec)),
|
||||
JSON_BUILD_PAIR_CONDITION(hr->password_change_max_usec != UINT64_MAX, "passwordChangeMaxUSec", JSON_BUILD_UNSIGNED(hr->password_change_max_usec)),
|
||||
JSON_BUILD_PAIR_CONDITION(hr->password_change_warn_usec != UINT64_MAX, "passwordChangeWarnUSec", JSON_BUILD_UNSIGNED(hr->password_change_warn_usec)),
|
||||
JSON_BUILD_PAIR_CONDITION(hr->password_change_inactive_usec != UINT64_MAX, "passwordChangeInactiveUSec", JSON_BUILD_UNSIGNED(hr->password_change_inactive_usec))));
|
||||
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
hr->mask = USER_RECORD_REGULAR |
|
||||
(!strv_isempty(hr->hashed_password) ? USER_RECORD_PRIVILEGED : 0);
|
||||
|
||||
*ret = TAKE_PTR(hr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int nss_spwd_for_passwd(const struct passwd *pwd, struct spwd *ret_spwd, char **ret_buffer) {
|
||||
size_t buflen = 4096;
|
||||
int r;
|
||||
|
||||
assert(pwd);
|
||||
assert(ret_spwd);
|
||||
assert(ret_buffer);
|
||||
|
||||
for (;;) {
|
||||
_cleanup_free_ char *buf = NULL;
|
||||
struct spwd spwd, *result;
|
||||
|
||||
buf = malloc(buflen);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
r = getspnam_r(pwd->pw_name, &spwd, buf, buflen, &result);
|
||||
if (r == 0) {
|
||||
if (!result)
|
||||
return -ESRCH;
|
||||
|
||||
*ret_spwd = *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_user_record_by_name(const char *name, UserRecord **ret) {
|
||||
_cleanup_free_ char *buf = NULL, *sbuf = NULL;
|
||||
struct passwd pwd, *result;
|
||||
bool incomplete = false;
|
||||
size_t buflen = 4096;
|
||||
struct spwd spwd;
|
||||
int r;
|
||||
|
||||
assert(name);
|
||||
assert(ret);
|
||||
|
||||
for (;;) {
|
||||
buf = malloc(buflen);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
r = getpwnam_r(name, &pwd, buf, buflen, &result);
|
||||
if (r == 0) {
|
||||
if (!result)
|
||||
return -ESRCH;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (r < 0)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getpwnam_r() returned a negative value");
|
||||
if (r != ERANGE)
|
||||
return -r;
|
||||
|
||||
if (buflen > SIZE_MAX / 2)
|
||||
return -ERANGE;
|
||||
|
||||
buflen *= 2;
|
||||
buf = mfree(buf);
|
||||
}
|
||||
|
||||
r = nss_spwd_for_passwd(result, &spwd, &sbuf);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to do shadow lookup for user %s, ignoring: %m", name);
|
||||
incomplete = ERRNO_IS_PRIVILEGE(r);
|
||||
}
|
||||
|
||||
r = nss_passwd_to_user_record(result, r >= 0 ? &spwd : NULL, ret);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
(*ret)->incomplete = incomplete;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int nss_user_record_by_uid(uid_t uid, UserRecord **ret) {
|
||||
_cleanup_free_ char *buf = NULL, *sbuf = NULL;
|
||||
struct passwd pwd, *result;
|
||||
bool incomplete = false;
|
||||
size_t buflen = 4096;
|
||||
struct spwd spwd;
|
||||
int r;
|
||||
|
||||
assert(ret);
|
||||
|
||||
for (;;) {
|
||||
buf = malloc(buflen);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
r = getpwuid_r(uid, &pwd, buf, buflen, &result);
|
||||
if (r == 0) {
|
||||
if (!result)
|
||||
return -ESRCH;
|
||||
|
||||
break;
|
||||
}
|
||||
if (r < 0)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getpwuid_r() returned a negative value");
|
||||
if (r != ERANGE)
|
||||
return -r;
|
||||
|
||||
if (buflen > SIZE_MAX / 2)
|
||||
return -ERANGE;
|
||||
|
||||
buflen *= 2;
|
||||
buf = mfree(buf);
|
||||
}
|
||||
|
||||
r = nss_spwd_for_passwd(result, &spwd, &sbuf);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Failed to do shadow lookup for UID " UID_FMT ", ignoring: %m", uid);
|
||||
incomplete = ERRNO_IS_PRIVILEGE(r);
|
||||
}
|
||||
|
||||
r = nss_passwd_to_user_record(result, r >= 0 ? &spwd : NULL, ret);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
(*ret)->incomplete = incomplete;
|
||||
return 0;
|
||||
}
|
15
src/shared/user-record-nss.h
Normal file
15
src/shared/user-record-nss.h
Normal file
@ -0,0 +1,15 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#include <pwd.h>
|
||||
#include <shadow.h>
|
||||
|
||||
#include "user-record.h"
|
||||
|
||||
/* Synthesizes a UserRecord object from NSS data */
|
||||
|
||||
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_user_record_by_name(const char *name, UserRecord **ret);
|
||||
int nss_user_record_by_uid(uid_t uid, UserRecord **ret);
|
466
src/shared/user-record-show.c
Normal file
466
src/shared/user-record-show.c
Normal file
@ -0,0 +1,466 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
|
||||
#include "format-util.h"
|
||||
#include "fs-util.h"
|
||||
#include "group-record.h"
|
||||
#include "process-util.h"
|
||||
#include "rlimit-util.h"
|
||||
#include "strv.h"
|
||||
#include "terminal-util.h"
|
||||
#include "user-record-show.h"
|
||||
#include "user-util.h"
|
||||
#include "userdb.h"
|
||||
|
||||
const char *user_record_state_color(const char *state) {
|
||||
if (STR_IN_SET(state, "unfixated", "absent"))
|
||||
return ansi_grey();
|
||||
else if (streq(state, "active"))
|
||||
return ansi_highlight_green();
|
||||
else if (streq(state, "locked"))
|
||||
return ansi_highlight_yellow();
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void user_record_show(UserRecord *hr, bool show_full_group_info) {
|
||||
const char *hd, *ip, *shell;
|
||||
UserStorage storage;
|
||||
usec_t t;
|
||||
size_t k;
|
||||
int r, b;
|
||||
|
||||
printf(" User name: %s\n",
|
||||
user_record_user_name_and_realm(hr));
|
||||
|
||||
if (hr->state) {
|
||||
const char *color;
|
||||
|
||||
color = user_record_state_color(hr->state);
|
||||
|
||||
printf(" State: %s%s%s\n",
|
||||
strempty(color), hr->state, color ? ansi_normal() : "");
|
||||
}
|
||||
|
||||
printf(" Disposition: %s\n", user_disposition_to_string(user_record_disposition(hr)));
|
||||
|
||||
if (hr->last_change_usec != USEC_INFINITY) {
|
||||
char buf[FORMAT_TIMESTAMP_MAX];
|
||||
printf(" Last Change: %s\n", format_timestamp(buf, sizeof(buf), hr->last_change_usec));
|
||||
}
|
||||
|
||||
if (hr->last_password_change_usec != USEC_INFINITY &&
|
||||
hr->last_password_change_usec != hr->last_change_usec) {
|
||||
char buf[FORMAT_TIMESTAMP_MAX];
|
||||
printf(" Last Passw.: %s\n", format_timestamp(buf, sizeof(buf), hr->last_password_change_usec));
|
||||
}
|
||||
|
||||
r = user_record_test_blocked(hr);
|
||||
switch (r) {
|
||||
|
||||
case -ESTALE:
|
||||
printf(" Login OK: %sno%s (last change time is in the future)\n", ansi_highlight_red(), ansi_normal());
|
||||
break;
|
||||
|
||||
case -ENOLCK:
|
||||
printf(" Login OK: %sno%s (record is locked)\n", ansi_highlight_red(), ansi_normal());
|
||||
break;
|
||||
|
||||
case -EL2HLT:
|
||||
printf(" Login OK: %sno%s (record not valid yet))\n", ansi_highlight_red(), ansi_normal());
|
||||
break;
|
||||
|
||||
case -EL3HLT:
|
||||
printf(" Login OK: %sno%s (record not valid anymore))\n", ansi_highlight_red(), ansi_normal());
|
||||
break;
|
||||
|
||||
default: {
|
||||
usec_t y;
|
||||
|
||||
if (r < 0) {
|
||||
errno = -r;
|
||||
printf(" Login OK: %sno%s (%m)\n", ansi_highlight_red(), ansi_normal());
|
||||
break;
|
||||
}
|
||||
|
||||
if (is_nologin_shell(user_record_shell(hr))) {
|
||||
printf(" Login OK: %sno%s (nologin shell)\n", ansi_highlight_red(), ansi_normal());
|
||||
break;
|
||||
}
|
||||
|
||||
y = user_record_ratelimit_next_try(hr);
|
||||
if (y != USEC_INFINITY && y > now(CLOCK_REALTIME)) {
|
||||
printf(" Login OK: %sno%s (ratelimit)\n", ansi_highlight_red(), ansi_normal());
|
||||
break;
|
||||
}
|
||||
|
||||
printf(" Login OK: %syes%s\n", ansi_highlight_green(), ansi_normal());
|
||||
break;
|
||||
}}
|
||||
|
||||
r = user_record_test_password_change_required(hr);
|
||||
switch (r) {
|
||||
|
||||
case -EKEYREVOKED:
|
||||
printf(" Password OK: %schange now%s\n", ansi_highlight_yellow(), ansi_normal());
|
||||
break;
|
||||
|
||||
case -EOWNERDEAD:
|
||||
printf(" Password OK: %sexpired%s (change now!)\n", ansi_highlight_yellow(), ansi_normal());
|
||||
break;
|
||||
|
||||
case -EKEYREJECTED:
|
||||
printf(" Password OK: %sexpired%s (for good)\n", ansi_highlight_red(), ansi_normal());
|
||||
break;
|
||||
|
||||
case -EKEYEXPIRED:
|
||||
printf(" Password OK: %sexpires soon%s\n", ansi_highlight_yellow(), ansi_normal());
|
||||
break;
|
||||
|
||||
case -ENETDOWN:
|
||||
printf(" Password OK: %sno timestamp%s\n", ansi_highlight_red(), ansi_normal());
|
||||
break;
|
||||
|
||||
case -EROFS:
|
||||
printf(" Password OK: %schange not permitted%s\n", ansi_highlight_yellow(), ansi_normal());
|
||||
break;
|
||||
|
||||
default:
|
||||
if (r < 0) {
|
||||
errno = -r;
|
||||
printf(" Password OK: %sno%s (%m)\n", ansi_highlight_yellow(), ansi_normal());
|
||||
break;
|
||||
}
|
||||
|
||||
printf(" Password OK: %syes%s\n", ansi_highlight_green(), ansi_normal());
|
||||
break;
|
||||
}
|
||||
|
||||
if (uid_is_valid(hr->uid))
|
||||
printf(" UID: " UID_FMT "\n", hr->uid);
|
||||
if (gid_is_valid(hr->gid)) {
|
||||
if (show_full_group_info) {
|
||||
_cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
|
||||
|
||||
r = groupdb_by_gid(hr->gid, 0, &gr);
|
||||
if (r < 0) {
|
||||
errno = -r;
|
||||
printf(" GID: " GID_FMT " (unresolvable: %m)\n", hr->gid);
|
||||
} else
|
||||
printf(" GID: " GID_FMT " (%s)\n", hr->gid, gr->group_name);
|
||||
} else
|
||||
printf(" GID: " GID_FMT "\n", hr->gid);
|
||||
} else if (uid_is_valid(hr->uid)) /* Show UID as GID if not separately configured */
|
||||
printf(" GID: " GID_FMT "\n", (gid_t) hr->uid);
|
||||
|
||||
if (show_full_group_info) {
|
||||
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
|
||||
|
||||
r = membershipdb_by_user(hr->user_name, 0, &iterator);
|
||||
if (r < 0) {
|
||||
errno = -r;
|
||||
printf(" Aux. Groups: (can't acquire: %m)\n");
|
||||
} else {
|
||||
const char *prefix = " Aux. Groups:";
|
||||
|
||||
for (;;) {
|
||||
_cleanup_free_ char *group = NULL;
|
||||
|
||||
r = membershipdb_iterator_get(iterator, NULL, &group);
|
||||
if (r == -ESRCH)
|
||||
break;
|
||||
if (r < 0) {
|
||||
errno = -r;
|
||||
printf("%s (can't iterate: %m)\n", prefix);
|
||||
break;
|
||||
}
|
||||
|
||||
printf("%s %s\n", prefix, group);
|
||||
prefix = " ";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hr->real_name && !streq(hr->real_name, hr->user_name))
|
||||
printf(" Real Name: %s\n", hr->real_name);
|
||||
|
||||
hd = user_record_home_directory(hr);
|
||||
if (hd)
|
||||
printf(" Directory: %s\n", hd);
|
||||
|
||||
storage = user_record_storage(hr);
|
||||
if (storage >= 0) /* Let's be political, and clarify which storage we like, and which we don't. About CIFS we don't complain. */
|
||||
printf(" Storage: %s%s\n", user_storage_to_string(storage),
|
||||
storage == USER_LUKS ? " (strong encryption)" :
|
||||
storage == USER_FSCRYPT ? " (weak encryption)" :
|
||||
IN_SET(storage, USER_DIRECTORY, USER_SUBVOLUME) ? " (no encryption)" : "");
|
||||
|
||||
ip = user_record_image_path(hr);
|
||||
if (ip && !streq_ptr(ip, hd))
|
||||
printf(" Image Path: %s\n", ip);
|
||||
|
||||
b = user_record_removable(hr);
|
||||
if (b >= 0)
|
||||
printf(" Removable: %s\n", yes_no(b));
|
||||
|
||||
shell = user_record_shell(hr);
|
||||
if (shell)
|
||||
printf(" Shell: %s\n", shell);
|
||||
|
||||
if (hr->email_address)
|
||||
printf(" Email: %s\n", hr->email_address);
|
||||
if (hr->location)
|
||||
printf(" Location: %s\n", hr->location);
|
||||
if (hr->password_hint)
|
||||
printf(" Passw. Hint: %s\n", hr->password_hint);
|
||||
if (hr->icon_name)
|
||||
printf(" Icon Name: %s\n", hr->icon_name);
|
||||
|
||||
if (hr->time_zone)
|
||||
printf(" Time Zone: %s\n", hr->time_zone);
|
||||
|
||||
if (hr->preferred_language)
|
||||
printf(" Language: %s\n", hr->preferred_language);
|
||||
|
||||
if (!strv_isempty(hr->environment)) {
|
||||
char **i;
|
||||
|
||||
STRV_FOREACH(i, hr->environment) {
|
||||
printf(i == hr->environment ?
|
||||
" Environment: %s\n" :
|
||||
" %s\n", *i);
|
||||
}
|
||||
}
|
||||
|
||||
if (hr->locked >= 0)
|
||||
printf(" Locked: %s\n", yes_no(hr->locked));
|
||||
|
||||
if (hr->not_before_usec != UINT64_MAX) {
|
||||
char buf[FORMAT_TIMESTAMP_MAX];
|
||||
printf(" Not Before: %s\n", format_timestamp(buf, sizeof(buf), hr->not_before_usec));
|
||||
}
|
||||
|
||||
if (hr->not_after_usec != UINT64_MAX) {
|
||||
char buf[FORMAT_TIMESTAMP_MAX];
|
||||
printf(" Not After: %s\n", format_timestamp(buf, sizeof(buf), hr->not_after_usec));
|
||||
}
|
||||
|
||||
if (hr->umask != MODE_INVALID)
|
||||
printf(" UMask: 0%03o\n", hr->umask);
|
||||
|
||||
if (nice_is_valid(hr->nice_level))
|
||||
printf(" Nice: %i\n", hr->nice_level);
|
||||
|
||||
for (int j = 0; j < _RLIMIT_MAX; j++) {
|
||||
if (hr->rlimits[j])
|
||||
printf(" Limit: RLIMIT_%s=%" PRIu64 ":%" PRIu64 "\n",
|
||||
rlimit_to_string(j), (uint64_t) hr->rlimits[j]->rlim_cur, (uint64_t) hr->rlimits[j]->rlim_max);
|
||||
}
|
||||
|
||||
if (hr->tasks_max != UINT64_MAX)
|
||||
printf(" Tasks Max: %" PRIu64 "\n", hr->tasks_max);
|
||||
|
||||
if (hr->memory_high != UINT64_MAX) {
|
||||
char buf[FORMAT_BYTES_MAX];
|
||||
printf(" Memory High: %s\n", format_bytes(buf, sizeof(buf), hr->memory_high));
|
||||
}
|
||||
|
||||
if (hr->memory_max != UINT64_MAX) {
|
||||
char buf[FORMAT_BYTES_MAX];
|
||||
printf(" Memory Max: %s\n", format_bytes(buf, sizeof(buf), hr->memory_max));
|
||||
}
|
||||
|
||||
if (hr->cpu_weight != UINT64_MAX)
|
||||
printf(" CPU Weight: %" PRIu64 "\n", hr->cpu_weight);
|
||||
|
||||
if (hr->io_weight != UINT64_MAX)
|
||||
printf(" IO Weight: %" PRIu64 "\n", hr->io_weight);
|
||||
|
||||
if (hr->access_mode != MODE_INVALID)
|
||||
printf(" Access Mode: 0%03oo\n", user_record_access_mode(hr));
|
||||
|
||||
if (storage == USER_LUKS) {
|
||||
printf("LUKS Discard: %s\n", yes_no(user_record_luks_discard(hr)));
|
||||
|
||||
if (!sd_id128_is_null(hr->luks_uuid))
|
||||
printf(" LUKS UUID: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(hr->luks_uuid));
|
||||
if (!sd_id128_is_null(hr->partition_uuid))
|
||||
printf(" Part UUID: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(hr->partition_uuid));
|
||||
if (!sd_id128_is_null(hr->file_system_uuid))
|
||||
printf(" FS UUID: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(hr->file_system_uuid));
|
||||
|
||||
if (hr->file_system_type)
|
||||
printf(" File System: %s\n", user_record_file_system_type(hr));
|
||||
|
||||
if (hr->luks_cipher)
|
||||
printf(" LUKS Cipher: %s\n", hr->luks_cipher);
|
||||
if (hr->luks_cipher_mode)
|
||||
printf(" Cipher Mode: %s\n", hr->luks_cipher_mode);
|
||||
if (hr->luks_volume_key_size != UINT64_MAX)
|
||||
printf(" Volume Key: %" PRIu64 "bit\n", hr->luks_volume_key_size * 8);
|
||||
|
||||
if (hr->luks_pbkdf_type)
|
||||
printf(" PBKDF Type: %s\n", hr->luks_pbkdf_type);
|
||||
if (hr->luks_pbkdf_hash_algorithm)
|
||||
printf(" PBKDF Hash: %s\n", hr->luks_pbkdf_hash_algorithm);
|
||||
if (hr->luks_pbkdf_time_cost_usec != UINT64_MAX) {
|
||||
char buf[FORMAT_TIMESPAN_MAX];
|
||||
printf(" PBKDF Time: %s\n", format_timespan(buf, sizeof(buf), hr->luks_pbkdf_time_cost_usec, 0));
|
||||
}
|
||||
if (hr->luks_pbkdf_memory_cost != UINT64_MAX) {
|
||||
char buf[FORMAT_BYTES_MAX];
|
||||
printf(" PBKDF Bytes: %s\n", format_bytes(buf, sizeof(buf), hr->luks_pbkdf_memory_cost));
|
||||
}
|
||||
if (hr->luks_pbkdf_parallel_threads != UINT64_MAX)
|
||||
printf("PBKDF Thread: %" PRIu64 "\n", hr->luks_pbkdf_parallel_threads);
|
||||
|
||||
} else if (storage == USER_CIFS) {
|
||||
|
||||
if (hr->cifs_service)
|
||||
printf("CIFS Service: %s\n", hr->cifs_service);
|
||||
}
|
||||
|
||||
if (hr->cifs_user_name)
|
||||
printf(" CIFS User: %s\n", user_record_cifs_user_name(hr));
|
||||
if (hr->cifs_domain)
|
||||
printf(" CIFS Domain: %s\n", hr->cifs_domain);
|
||||
|
||||
if (storage != USER_CLASSIC)
|
||||
printf(" Mount Flags: %s %s %s\n",
|
||||
hr->nosuid ? "nosuid" : "suid",
|
||||
hr->nodev ? "nodev" : "dev",
|
||||
hr->noexec ? "noexec" : "exec");
|
||||
|
||||
if (hr->skeleton_directory)
|
||||
printf(" Skel. Dir.: %s\n", user_record_skeleton_directory(hr));
|
||||
|
||||
if (hr->disk_size != UINT64_MAX) {
|
||||
char buf[FORMAT_BYTES_MAX];
|
||||
printf(" Disk Size: %s\n", format_bytes(buf, sizeof(buf), hr->disk_size));
|
||||
}
|
||||
|
||||
if (hr->disk_usage != UINT64_MAX) {
|
||||
char buf[FORMAT_BYTES_MAX];
|
||||
printf(" Disk Usage: %s\n", format_bytes(buf, sizeof(buf), hr->disk_usage));
|
||||
}
|
||||
|
||||
if (hr->disk_free != UINT64_MAX) {
|
||||
char buf[FORMAT_BYTES_MAX];
|
||||
printf(" Disk Free: %s\n", format_bytes(buf, sizeof(buf), hr->disk_free));
|
||||
}
|
||||
|
||||
if (hr->disk_floor != UINT64_MAX) {
|
||||
char buf[FORMAT_BYTES_MAX];
|
||||
printf(" Disk Floor: %s\n", format_bytes(buf, sizeof(buf), hr->disk_floor));
|
||||
}
|
||||
|
||||
if (hr->disk_ceiling != UINT64_MAX) {
|
||||
char buf[FORMAT_BYTES_MAX];
|
||||
printf("Disk Ceiling: %s\n", format_bytes(buf, sizeof(buf), hr->disk_ceiling));
|
||||
}
|
||||
|
||||
if (hr->good_authentication_counter != UINT64_MAX)
|
||||
printf(" Good Auth.: %" PRIu64 "\n", hr->good_authentication_counter);
|
||||
|
||||
if (hr->last_good_authentication_usec != UINT64_MAX) {
|
||||
char buf[FORMAT_TIMESTAMP_MAX];
|
||||
printf(" Last Good: %s\n", format_timestamp(buf, sizeof(buf), hr->last_good_authentication_usec));
|
||||
}
|
||||
|
||||
if (hr->bad_authentication_counter != UINT64_MAX)
|
||||
printf(" Bad Auth.: %" PRIu64 "\n", hr->bad_authentication_counter);
|
||||
|
||||
if (hr->last_bad_authentication_usec != UINT64_MAX) {
|
||||
char buf[FORMAT_TIMESTAMP_MAX];
|
||||
printf(" Last Bad: %s\n", format_timestamp(buf, sizeof(buf), hr->last_bad_authentication_usec));
|
||||
}
|
||||
|
||||
t = user_record_ratelimit_next_try(hr);
|
||||
if (t != USEC_INFINITY) {
|
||||
usec_t n = now(CLOCK_REALTIME);
|
||||
|
||||
if (t <= n)
|
||||
printf(" Next Try: anytime\n");
|
||||
else {
|
||||
char buf[FORMAT_TIMESPAN_MAX];
|
||||
printf(" Next Try: %sin %s%s\n",
|
||||
ansi_highlight_red(),
|
||||
format_timespan(buf, sizeof(buf), t - n, USEC_PER_SEC),
|
||||
ansi_normal());
|
||||
}
|
||||
}
|
||||
|
||||
if (storage != USER_CLASSIC) {
|
||||
char buf[FORMAT_TIMESPAN_MAX];
|
||||
printf(" Auth. Limit: %" PRIu64 " attempts per %s\n", user_record_ratelimit_burst(hr),
|
||||
format_timespan(buf, sizeof(buf), user_record_ratelimit_interval_usec(hr), 0));
|
||||
}
|
||||
|
||||
if (hr->enforce_password_policy >= 0)
|
||||
printf(" Passwd Pol.: %s\n", yes_no(hr->enforce_password_policy));
|
||||
|
||||
if (hr->password_change_min_usec != UINT64_MAX ||
|
||||
hr->password_change_max_usec != UINT64_MAX ||
|
||||
hr->password_change_warn_usec != UINT64_MAX ||
|
||||
hr->password_change_inactive_usec != UINT64_MAX) {
|
||||
|
||||
char buf[FORMAT_TIMESPAN_MAX];
|
||||
printf(" Passwd Chg.:");
|
||||
|
||||
if (hr->password_change_min_usec != UINT64_MAX) {
|
||||
printf(" min %s", format_timespan(buf, sizeof(buf), hr->password_change_min_usec, 0));
|
||||
|
||||
if (hr->password_change_max_usec != UINT64_MAX)
|
||||
printf(" …");
|
||||
}
|
||||
|
||||
if (hr->password_change_max_usec != UINT64_MAX)
|
||||
printf(" max %s", format_timespan(buf, sizeof(buf), hr->password_change_max_usec, 0));
|
||||
|
||||
if (hr->password_change_warn_usec != UINT64_MAX)
|
||||
printf("/warn %s", format_timespan(buf, sizeof(buf), hr->password_change_warn_usec, 0));
|
||||
|
||||
if (hr->password_change_inactive_usec != UINT64_MAX)
|
||||
printf("/inactive %s", format_timespan(buf, sizeof(buf), hr->password_change_inactive_usec, 0));
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
if (hr->password_change_now >= 0)
|
||||
printf("Pas. Ch. Now: %s\n", yes_no(hr->password_change_now));
|
||||
|
||||
if (!strv_isempty(hr->ssh_authorized_keys))
|
||||
printf("SSH Pub. Key: %zu\n", strv_length(hr->ssh_authorized_keys));
|
||||
|
||||
if (!strv_isempty(hr->pkcs11_token_uri)) {
|
||||
char **i;
|
||||
|
||||
STRV_FOREACH(i, hr->pkcs11_token_uri)
|
||||
printf(i == hr->pkcs11_token_uri ?
|
||||
" Sec. Token: %s\n" :
|
||||
" %s\n", *i);
|
||||
}
|
||||
|
||||
k = strv_length(hr->hashed_password);
|
||||
if (k == 0)
|
||||
printf(" Passwords: %snone%s\n",
|
||||
user_record_disposition(hr) == USER_REGULAR ? ansi_highlight_yellow() : ansi_normal(), ansi_normal());
|
||||
else
|
||||
printf(" Passwords: %zu\n", k);
|
||||
|
||||
if (hr->signed_locally >= 0)
|
||||
printf(" Local Sig.: %s\n", yes_no(hr->signed_locally));
|
||||
|
||||
if (hr->stop_delay_usec != UINT64_MAX) {
|
||||
char buf[FORMAT_TIMESPAN_MAX];
|
||||
printf(" Stop Delay: %s\n", format_timespan(buf, sizeof(buf), hr->stop_delay_usec, 0));
|
||||
}
|
||||
|
||||
if (hr->auto_login >= 0)
|
||||
printf("Autom. Login: %s\n", yes_no(hr->auto_login));
|
||||
|
||||
if (hr->kill_processes >= 0)
|
||||
printf(" Kill Proc.: %s\n", yes_no(hr->kill_processes));
|
||||
|
||||
if (hr->service)
|
||||
printf(" Service: %s\n", hr->service);
|
||||
}
|
8
src/shared/user-record-show.h
Normal file
8
src/shared/user-record-show.h
Normal file
@ -0,0 +1,8 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#include "user-record.h"
|
||||
|
||||
const char *user_record_state_color(const char *state);
|
||||
|
||||
void user_record_show(UserRecord *hr, bool show_full_group_info);
|
1883
src/shared/user-record.c
Normal file
1883
src/shared/user-record.c
Normal file
File diff suppressed because it is too large
Load Diff
375
src/shared/user-record.h
Normal file
375
src/shared/user-record.h
Normal file
@ -0,0 +1,375 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "sd-id128.h"
|
||||
|
||||
#include "json.h"
|
||||
#include "missing_resource.h"
|
||||
#include "time-util.h"
|
||||
|
||||
/* But 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)
|
||||
|
||||
/* The default disk size to use when nothing else is specified, relative to free disk space */
|
||||
#define USER_DISK_SIZE_DEFAULT_PERCENT 85
|
||||
|
||||
typedef enum UserDisposition {
|
||||
USER_INTRINSIC, /* root and nobody */
|
||||
USER_SYSTEM, /* statically allocated users for system services */
|
||||
USER_DYNAMIC, /* dynamically allocated users for system services */
|
||||
USER_REGULAR, /* regular (typically human users) */
|
||||
USER_CONTAINER, /* UID ranges allocated for container uses */
|
||||
USER_RESERVED, /* Range above 2^31 */
|
||||
_USER_DISPOSITION_MAX,
|
||||
_USER_DISPOSITION_INVALID = -1,
|
||||
} UserDisposition;
|
||||
|
||||
typedef enum UserHomeStorage {
|
||||
USER_CLASSIC,
|
||||
USER_LUKS,
|
||||
USER_DIRECTORY, /* A directory, and a .identity file in it, which USER_CLASSIC lacks */
|
||||
USER_SUBVOLUME,
|
||||
USER_FSCRYPT,
|
||||
USER_CIFS,
|
||||
_USER_STORAGE_MAX,
|
||||
_USER_STORAGE_INVALID = -1
|
||||
} UserStorage;
|
||||
|
||||
typedef enum UserRecordMask {
|
||||
/* The various sections an identity record may have, as bit mask */
|
||||
USER_RECORD_REGULAR = 1U << 0,
|
||||
USER_RECORD_SECRET = 1U << 1,
|
||||
USER_RECORD_PRIVILEGED = 1U << 2,
|
||||
USER_RECORD_PER_MACHINE = 1U << 3,
|
||||
USER_RECORD_BINDING = 1U << 4,
|
||||
USER_RECORD_STATUS = 1U << 5,
|
||||
USER_RECORD_SIGNATURE = 1U << 6,
|
||||
_USER_RECORD_MASK_MAX = (1U << 7)-1
|
||||
} UserRecordMask;
|
||||
|
||||
typedef enum UserRecordLoadFlags {
|
||||
/* A set of flags used while loading a user record from JSON data. We leave the lower 6 bits free,
|
||||
* just as a safety precaution so that we can detect borked conversions between UserRecordMask and
|
||||
* UserRecordLoadFlags. */
|
||||
|
||||
/* What to require */
|
||||
USER_RECORD_REQUIRE_REGULAR = USER_RECORD_REGULAR << 7,
|
||||
USER_RECORD_REQUIRE_SECRET = USER_RECORD_SECRET << 7,
|
||||
USER_RECORD_REQUIRE_PRIVILEGED = USER_RECORD_PRIVILEGED << 7,
|
||||
USER_RECORD_REQUIRE_PER_MACHINE = USER_RECORD_PER_MACHINE << 7,
|
||||
USER_RECORD_REQUIRE_BINDING = USER_RECORD_BINDING << 7,
|
||||
USER_RECORD_REQUIRE_STATUS = USER_RECORD_STATUS << 7,
|
||||
USER_RECORD_REQUIRE_SIGNATURE = USER_RECORD_SIGNATURE << 7,
|
||||
|
||||
/* What to allow */
|
||||
USER_RECORD_ALLOW_REGULAR = USER_RECORD_REGULAR << 14,
|
||||
USER_RECORD_ALLOW_SECRET = USER_RECORD_SECRET << 14,
|
||||
USER_RECORD_ALLOW_PRIVILEGED = USER_RECORD_PRIVILEGED << 14,
|
||||
USER_RECORD_ALLOW_PER_MACHINE = USER_RECORD_PER_MACHINE << 14,
|
||||
USER_RECORD_ALLOW_BINDING = USER_RECORD_BINDING << 14,
|
||||
USER_RECORD_ALLOW_STATUS = USER_RECORD_STATUS << 14,
|
||||
USER_RECORD_ALLOW_SIGNATURE = USER_RECORD_SIGNATURE << 14,
|
||||
|
||||
/* What to strip */
|
||||
USER_RECORD_STRIP_REGULAR = USER_RECORD_REGULAR << 21,
|
||||
USER_RECORD_STRIP_SECRET = USER_RECORD_SECRET << 21,
|
||||
USER_RECORD_STRIP_PRIVILEGED = USER_RECORD_PRIVILEGED << 21,
|
||||
USER_RECORD_STRIP_PER_MACHINE = USER_RECORD_PER_MACHINE << 21,
|
||||
USER_RECORD_STRIP_BINDING = USER_RECORD_BINDING << 21,
|
||||
USER_RECORD_STRIP_STATUS = USER_RECORD_STATUS << 21,
|
||||
USER_RECORD_STRIP_SIGNATURE = USER_RECORD_SIGNATURE << 21,
|
||||
|
||||
/* Some special combinations that deserve explicit names */
|
||||
USER_RECORD_LOAD_FULL = USER_RECORD_REQUIRE_REGULAR |
|
||||
USER_RECORD_ALLOW_SECRET |
|
||||
USER_RECORD_ALLOW_PRIVILEGED |
|
||||
USER_RECORD_ALLOW_PER_MACHINE |
|
||||
USER_RECORD_ALLOW_BINDING |
|
||||
USER_RECORD_ALLOW_STATUS |
|
||||
USER_RECORD_ALLOW_SIGNATURE,
|
||||
|
||||
USER_RECORD_LOAD_REFUSE_SECRET = USER_RECORD_REQUIRE_REGULAR |
|
||||
USER_RECORD_ALLOW_PRIVILEGED |
|
||||
USER_RECORD_ALLOW_PER_MACHINE |
|
||||
USER_RECORD_ALLOW_BINDING |
|
||||
USER_RECORD_ALLOW_STATUS |
|
||||
USER_RECORD_ALLOW_SIGNATURE,
|
||||
|
||||
USER_RECORD_LOAD_MASK_SECRET = USER_RECORD_REQUIRE_REGULAR |
|
||||
USER_RECORD_ALLOW_PRIVILEGED |
|
||||
USER_RECORD_ALLOW_PER_MACHINE |
|
||||
USER_RECORD_ALLOW_BINDING |
|
||||
USER_RECORD_ALLOW_STATUS |
|
||||
USER_RECORD_ALLOW_SIGNATURE |
|
||||
USER_RECORD_STRIP_SECRET,
|
||||
|
||||
USER_RECORD_EXTRACT_SECRET = USER_RECORD_REQUIRE_SECRET |
|
||||
USER_RECORD_STRIP_REGULAR |
|
||||
USER_RECORD_STRIP_PRIVILEGED |
|
||||
USER_RECORD_STRIP_PER_MACHINE |
|
||||
USER_RECORD_STRIP_BINDING |
|
||||
USER_RECORD_STRIP_STATUS |
|
||||
USER_RECORD_STRIP_SIGNATURE,
|
||||
|
||||
USER_RECORD_LOAD_SIGNABLE = USER_RECORD_REQUIRE_REGULAR |
|
||||
USER_RECORD_ALLOW_PRIVILEGED |
|
||||
USER_RECORD_ALLOW_PER_MACHINE,
|
||||
|
||||
USER_RECORD_EXTRACT_SIGNABLE = USER_RECORD_LOAD_SIGNABLE |
|
||||
USER_RECORD_STRIP_SECRET |
|
||||
USER_RECORD_STRIP_BINDING |
|
||||
USER_RECORD_STRIP_STATUS |
|
||||
USER_RECORD_STRIP_SIGNATURE,
|
||||
|
||||
USER_RECORD_LOAD_EMBEDDED = USER_RECORD_REQUIRE_REGULAR |
|
||||
USER_RECORD_ALLOW_PRIVILEGED |
|
||||
USER_RECORD_ALLOW_PER_MACHINE |
|
||||
USER_RECORD_ALLOW_SIGNATURE,
|
||||
|
||||
USER_RECORD_EXTRACT_EMBEDDED = USER_RECORD_LOAD_EMBEDDED |
|
||||
USER_RECORD_STRIP_SECRET |
|
||||
USER_RECORD_STRIP_BINDING |
|
||||
USER_RECORD_STRIP_STATUS,
|
||||
|
||||
/* Whether to log about loader errors beyond LOG_DEBUG */
|
||||
USER_RECORD_LOG = 1U << 28,
|
||||
|
||||
/* Whether to ignore errors and load what we can */
|
||||
USER_RECORD_PERMISSIVE = 1U << 29,
|
||||
} UserRecordLoadFlags;
|
||||
|
||||
static inline UserRecordLoadFlags USER_RECORD_REQUIRE(UserRecordMask m) {
|
||||
assert((m & ~_USER_RECORD_MASK_MAX) == 0);
|
||||
return m << 7;
|
||||
}
|
||||
|
||||
static inline UserRecordLoadFlags USER_RECORD_ALLOW(UserRecordMask m) {
|
||||
assert((m & ~_USER_RECORD_MASK_MAX) == 0);
|
||||
return m << 14;
|
||||
}
|
||||
|
||||
static inline UserRecordLoadFlags USER_RECORD_STRIP(UserRecordMask m) {
|
||||
assert((m & ~_USER_RECORD_MASK_MAX) == 0);
|
||||
return m << 21;
|
||||
}
|
||||
|
||||
static inline UserRecordMask USER_RECORD_REQUIRE_MASK(UserRecordLoadFlags f) {
|
||||
return (f >> 7) & _USER_RECORD_MASK_MAX;
|
||||
}
|
||||
|
||||
static inline UserRecordMask USER_RECORD_ALLOW_MASK(UserRecordLoadFlags f) {
|
||||
return ((f >> 14) & _USER_RECORD_MASK_MAX) | USER_RECORD_REQUIRE_MASK(f);
|
||||
}
|
||||
|
||||
static inline UserRecordMask USER_RECORD_STRIP_MASK(UserRecordLoadFlags f) {
|
||||
return (f >> 21) & _USER_RECORD_MASK_MAX;
|
||||
}
|
||||
|
||||
static inline JsonDispatchFlags USER_RECORD_LOAD_FLAGS_TO_JSON_DISPATCH_FLAGS(UserRecordLoadFlags flags) {
|
||||
return (FLAGS_SET(flags, USER_RECORD_LOG) ? JSON_LOG : 0) |
|
||||
(FLAGS_SET(flags, USER_RECORD_PERMISSIVE) ? JSON_PERMISSIVE : 0);
|
||||
}
|
||||
|
||||
typedef struct Pkcs11EncryptedKey {
|
||||
/* The encrypted passphrase, which can be decrypted with the private key indicated below */
|
||||
void *data;
|
||||
size_t size;
|
||||
|
||||
/* Where to find the private key to decrypt the encrypted passphrase above */
|
||||
char *uri;
|
||||
|
||||
/* What to test the decrypted passphrase against to allow access (classic UNIX password hash). Note
|
||||
* that the decrypted passphrase is also used for unlocking LUKS and fscrypt, and if the account is
|
||||
* backed by LUKS or fscrypt the hashed password is only an additional layer of authentication, not
|
||||
* the only. */
|
||||
char *hashed_password;
|
||||
} Pkcs11EncryptedKey;
|
||||
|
||||
typedef struct UserRecord {
|
||||
/* The following three fields are not part of the JSON record */
|
||||
unsigned n_ref;
|
||||
UserRecordMask mask;
|
||||
bool incomplete; /* incomplete due to security restrictions. */
|
||||
|
||||
char *user_name;
|
||||
char *realm;
|
||||
char *user_name_and_realm_auto; /* the user_name field concatenated with '@' and the realm, if the latter is defined */
|
||||
char *real_name;
|
||||
char *email_address;
|
||||
char *password_hint;
|
||||
char *icon_name;
|
||||
char *location;
|
||||
|
||||
UserDisposition disposition;
|
||||
uint64_t last_change_usec;
|
||||
uint64_t last_password_change_usec;
|
||||
|
||||
char *shell;
|
||||
mode_t umask;
|
||||
char **environment;
|
||||
char *time_zone;
|
||||
char *preferred_language;
|
||||
int nice_level;
|
||||
struct rlimit *rlimits[_RLIMIT_MAX];
|
||||
|
||||
int locked; /* prohibit activation in general */
|
||||
uint64_t not_before_usec; /* prohibit activation before this unix time */
|
||||
uint64_t not_after_usec; /* prohibit activation after this unix time */
|
||||
|
||||
UserStorage storage;
|
||||
uint64_t disk_size;
|
||||
uint64_t disk_size_relative; /* Disk size, relative to the free bytes of the medium, normalized to UINT32_MAX = 100% */
|
||||
char *skeleton_directory;
|
||||
mode_t access_mode;
|
||||
|
||||
uint64_t tasks_max;
|
||||
uint64_t memory_high;
|
||||
uint64_t memory_max;
|
||||
uint64_t cpu_weight;
|
||||
uint64_t io_weight;
|
||||
|
||||
bool nosuid;
|
||||
bool nodev;
|
||||
bool noexec;
|
||||
|
||||
char **hashed_password;
|
||||
char **ssh_authorized_keys;
|
||||
char **password;
|
||||
char **pkcs11_pin;
|
||||
|
||||
char *cifs_domain;
|
||||
char *cifs_user_name;
|
||||
char *cifs_service;
|
||||
|
||||
char *image_path;
|
||||
char *image_path_auto; /* when none is configured explicitly, this is where we place the implicit image */
|
||||
char *home_directory;
|
||||
char *home_directory_auto; /* when none is set explicitly, this is where we place the implicit home directory */
|
||||
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
|
||||
char **member_of;
|
||||
|
||||
char *file_system_type;
|
||||
sd_id128_t partition_uuid;
|
||||
sd_id128_t luks_uuid;
|
||||
sd_id128_t file_system_uuid;
|
||||
|
||||
int luks_discard;
|
||||
char *luks_cipher;
|
||||
char *luks_cipher_mode;
|
||||
uint64_t luks_volume_key_size;
|
||||
char *luks_pbkdf_hash_algorithm;
|
||||
char *luks_pbkdf_type;
|
||||
uint64_t luks_pbkdf_time_cost_usec;
|
||||
uint64_t luks_pbkdf_memory_cost;
|
||||
uint64_t luks_pbkdf_parallel_threads;
|
||||
|
||||
uint64_t disk_usage;
|
||||
uint64_t disk_free;
|
||||
uint64_t disk_ceiling;
|
||||
uint64_t disk_floor;
|
||||
|
||||
char *state;
|
||||
char *service;
|
||||
int signed_locally;
|
||||
|
||||
uint64_t good_authentication_counter;
|
||||
uint64_t bad_authentication_counter;
|
||||
uint64_t last_good_authentication_usec;
|
||||
uint64_t last_bad_authentication_usec;
|
||||
|
||||
uint64_t ratelimit_begin_usec;
|
||||
uint64_t ratelimit_count;
|
||||
uint64_t ratelimit_interval_usec;
|
||||
uint64_t ratelimit_burst;
|
||||
|
||||
int removable;
|
||||
int enforce_password_policy;
|
||||
int auto_login;
|
||||
|
||||
uint64_t stop_delay_usec; /* How long to leave systemd --user around on log-out */
|
||||
int kill_processes; /* Whether to kill user processes forcibly on log-out */
|
||||
|
||||
/* The following exist mostly so that we can cover the full /etc/shadow set of fields */
|
||||
uint64_t password_change_min_usec; /* maps to .sp_min */
|
||||
uint64_t password_change_max_usec; /* maps to .sp_max */
|
||||
uint64_t password_change_warn_usec; /* maps to .sp_warn */
|
||||
uint64_t password_change_inactive_usec; /* maps to .sp_inact */
|
||||
int password_change_now; /* Require a password change immediately on next login (.sp_lstchg = 0) */
|
||||
|
||||
char **pkcs11_token_uri;
|
||||
Pkcs11EncryptedKey *pkcs11_encrypted_key;
|
||||
size_t n_pkcs11_encrypted_key;
|
||||
int pkcs11_protected_authentication_path_permitted;
|
||||
|
||||
JsonVariant *json;
|
||||
} UserRecord;
|
||||
|
||||
UserRecord* user_record_new(void);
|
||||
UserRecord* user_record_ref(UserRecord *h);
|
||||
UserRecord* user_record_unref(UserRecord *h);
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(UserRecord*, user_record_unref);
|
||||
|
||||
int user_record_load(UserRecord *h, JsonVariant *v, UserRecordLoadFlags flags);
|
||||
int user_record_build(UserRecord **ret, ...);
|
||||
|
||||
const char *user_record_user_name_and_realm(UserRecord *h);
|
||||
UserStorage user_record_storage(UserRecord *h);
|
||||
const char *user_record_file_system_type(UserRecord *h);
|
||||
const char *user_record_skeleton_directory(UserRecord *h);
|
||||
mode_t user_record_access_mode(UserRecord *h);
|
||||
const char *user_record_home_directory(UserRecord *h);
|
||||
const char *user_record_image_path(UserRecord *h);
|
||||
unsigned long user_record_mount_flags(UserRecord *h);
|
||||
const char *user_record_cifs_user_name(UserRecord *h);
|
||||
const char *user_record_shell(UserRecord *h);
|
||||
const char *user_record_real_name(UserRecord *h);
|
||||
bool user_record_luks_discard(UserRecord *h);
|
||||
const char *user_record_luks_cipher(UserRecord *h);
|
||||
const char *user_record_luks_cipher_mode(UserRecord *h);
|
||||
uint64_t user_record_luks_volume_key_size(UserRecord *h);
|
||||
const char* user_record_luks_pbkdf_type(UserRecord *h);
|
||||
usec_t user_record_luks_pbkdf_time_cost_usec(UserRecord *h);
|
||||
uint64_t user_record_luks_pbkdf_memory_cost(UserRecord *h);
|
||||
uint64_t user_record_luks_pbkdf_parallel_threads(UserRecord *h);
|
||||
const char *user_record_luks_pbkdf_hash_algorithm(UserRecord *h);
|
||||
gid_t user_record_gid(UserRecord *h);
|
||||
UserDisposition user_record_disposition(UserRecord *h);
|
||||
int user_record_removable(UserRecord *h);
|
||||
usec_t user_record_ratelimit_interval_usec(UserRecord *h);
|
||||
uint64_t user_record_ratelimit_burst(UserRecord *h);
|
||||
bool user_record_can_authenticate(UserRecord *h);
|
||||
|
||||
bool user_record_equal(UserRecord *a, UserRecord *b);
|
||||
bool user_record_compatible(UserRecord *a, UserRecord *b);
|
||||
int user_record_compare_last_change(UserRecord *a, UserRecord *b);
|
||||
|
||||
usec_t user_record_ratelimit_next_try(UserRecord *h);
|
||||
|
||||
int user_record_clone(UserRecord *h, UserRecordLoadFlags flags, UserRecord **ret);
|
||||
int user_record_masked_equal(UserRecord *a, UserRecord *b, UserRecordMask mask);
|
||||
|
||||
int user_record_test_blocked(UserRecord *h);
|
||||
int user_record_test_password_change_required(UserRecord *h);
|
||||
|
||||
/* The following six are user by group-record.c, that's why we export them here */
|
||||
int json_dispatch_realm(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
|
||||
int json_dispatch_user_group_list(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
|
||||
int json_dispatch_user_disposition(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata);
|
||||
|
||||
int per_machine_id_match(JsonVariant *ids, JsonDispatchFlags flags);
|
||||
int per_machine_hostname_match(JsonVariant *hns, JsonDispatchFlags flags);
|
||||
int user_group_record_mangle(JsonVariant *v, UserRecordLoadFlags load_flags, JsonVariant **ret_variant, UserRecordMask *ret_mask);
|
||||
|
||||
const char* user_storage_to_string(UserStorage t) _const_;
|
||||
UserStorage user_storage_from_string(const char *s) _pure_;
|
||||
|
||||
const char* user_disposition_to_string(UserDisposition t) _const_;
|
||||
UserDisposition user_disposition_from_string(const char *s) _pure_;
|
1347
src/shared/userdb.c
Normal file
1347
src/shared/userdb.c
Normal file
File diff suppressed because it is too large
Load Diff
41
src/shared/userdb.h
Normal file
41
src/shared/userdb.h
Normal file
@ -0,0 +1,41 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#include "group-record.h"
|
||||
#include "user-record.h"
|
||||
|
||||
/* Inquire local services for user/group records */
|
||||
|
||||
typedef struct UserDBIterator UserDBIterator;
|
||||
|
||||
UserDBIterator *userdb_iterator_free(UserDBIterator *iterator);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(UserDBIterator*, userdb_iterator_free);
|
||||
|
||||
typedef enum UserDBFlags {
|
||||
USERDB_AVOID_NSS = 1 << 0, /* don't do client-side nor server-side NSS */
|
||||
USERDB_AVOID_DYNAMIC_USER = 1 << 1, /* exclude looking up in io.systemd.DynamicUser */
|
||||
USERDB_AVOID_MULTIPLEXER = 1 << 2, /* exclude looking up via io.systemd.Multiplexer */
|
||||
USERDB_DONT_SYNTHESIZE = 1 << 3, /* don't synthesize root/nobody */
|
||||
} UserDBFlags;
|
||||
|
||||
int userdb_by_name(const char *name, UserDBFlags flags, UserRecord **ret);
|
||||
int userdb_by_uid(uid_t uid, UserDBFlags flags, UserRecord **ret);
|
||||
int userdb_all(UserDBFlags flags, UserDBIterator **ret);
|
||||
int userdb_iterator_get(UserDBIterator *iterator, UserRecord **ret);
|
||||
|
||||
int groupdb_by_name(const char *name, UserDBFlags flags, GroupRecord **ret);
|
||||
int groupdb_by_gid(gid_t gid, UserDBFlags flags, GroupRecord **ret);
|
||||
int groupdb_all(UserDBFlags flags, UserDBIterator **ret);
|
||||
int groupdb_iterator_get(UserDBIterator *iterator, GroupRecord **ret);
|
||||
|
||||
int membershipdb_by_user(const char *name, UserDBFlags flags, UserDBIterator **ret);
|
||||
int membershipdb_by_group(const char *name, UserDBFlags flags, UserDBIterator **ret);
|
||||
int membershipdb_all(UserDBFlags flags, UserDBIterator **ret);
|
||||
int membershipdb_iterator_get(UserDBIterator *iterator, char **user, char **group);
|
||||
int membershipdb_by_group_strv(const char *name, UserDBFlags flags, char ***ret);
|
||||
|
||||
int userdb_nss_compat_is_enabled(void);
|
||||
int userdb_nss_compat_disable(void);
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "format-util.h"
|
||||
#include "libcrypt-util.h"
|
||||
#include "log.h"
|
||||
#include "macro.h"
|
||||
#include "memory-util.h"
|
||||
|
15
src/userdb/meson.build
Normal file
15
src/userdb/meson.build
Normal file
@ -0,0 +1,15 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1+
|
||||
|
||||
systemd_userwork_sources = files('''
|
||||
userwork.c
|
||||
'''.split())
|
||||
|
||||
systemd_userdbd_sources = files('''
|
||||
userdbd-manager.c
|
||||
userdbd-manager.h
|
||||
userdbd.c
|
||||
'''.split())
|
||||
|
||||
userdbctl_sources = files('''
|
||||
userdbctl.c
|
||||
'''.split())
|
790
src/userdb/userdbctl.c
Normal file
790
src/userdb/userdbctl.c
Normal file
@ -0,0 +1,790 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
|
||||
#include <getopt.h>
|
||||
#include <utmp.h>
|
||||
|
||||
#include "dirent-util.h"
|
||||
#include "errno-list.h"
|
||||
#include "fd-util.h"
|
||||
#include "format-table.h"
|
||||
#include "format-util.h"
|
||||
#include "group-record-show.h"
|
||||
#include "main-func.h"
|
||||
#include "pager.h"
|
||||
#include "parse-util.h"
|
||||
#include "pretty-print.h"
|
||||
#include "socket-util.h"
|
||||
#include "strv.h"
|
||||
#include "terminal-util.h"
|
||||
#include "user-record-show.h"
|
||||
#include "user-util.h"
|
||||
#include "userdb.h"
|
||||
#include "verbs.h"
|
||||
|
||||
static enum {
|
||||
OUTPUT_CLASSIC,
|
||||
OUTPUT_TABLE,
|
||||
OUTPUT_FRIENDLY,
|
||||
OUTPUT_JSON,
|
||||
_OUTPUT_INVALID = -1
|
||||
} arg_output = _OUTPUT_INVALID;
|
||||
|
||||
static PagerFlags arg_pager_flags = 0;
|
||||
static bool arg_legend = true;
|
||||
static char** arg_services = NULL;
|
||||
static UserDBFlags arg_userdb_flags = 0;
|
||||
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_services, strv_freep);
|
||||
|
||||
static int show_user(UserRecord *ur, Table *table) {
|
||||
int r;
|
||||
|
||||
assert(ur);
|
||||
|
||||
switch (arg_output) {
|
||||
|
||||
case OUTPUT_CLASSIC:
|
||||
if (!uid_is_valid(ur->uid))
|
||||
break;
|
||||
|
||||
printf("%s:x:" UID_FMT ":" GID_FMT ":%s:%s:%s\n",
|
||||
ur->user_name,
|
||||
ur->uid,
|
||||
user_record_gid(ur),
|
||||
strempty(user_record_real_name(ur)),
|
||||
user_record_home_directory(ur),
|
||||
user_record_shell(ur));
|
||||
|
||||
break;
|
||||
|
||||
case OUTPUT_JSON:
|
||||
json_variant_dump(ur->json, JSON_FORMAT_COLOR_AUTO|JSON_FORMAT_PRETTY, NULL, 0);
|
||||
break;
|
||||
|
||||
case OUTPUT_FRIENDLY:
|
||||
user_record_show(ur, true);
|
||||
|
||||
if (ur->incomplete) {
|
||||
fflush(stdout);
|
||||
log_warning("Warning: lacking rights to acquire privileged fields of user record of '%s', output incomplete.", ur->user_name);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case OUTPUT_TABLE:
|
||||
assert(table);
|
||||
|
||||
r = table_add_many(
|
||||
table,
|
||||
TABLE_STRING, ur->user_name,
|
||||
TABLE_STRING, user_disposition_to_string(user_record_disposition(ur)),
|
||||
TABLE_UID, ur->uid,
|
||||
TABLE_GID, user_record_gid(ur),
|
||||
TABLE_STRING, empty_to_null(ur->real_name),
|
||||
TABLE_STRING, user_record_home_directory(ur),
|
||||
TABLE_STRING, user_record_shell(ur),
|
||||
TABLE_INT, (int) user_record_disposition(ur));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to add row to table: %m");
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
assert_not_reached("Unexpected output mode");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int display_user(int argc, char *argv[], void *userdata) {
|
||||
_cleanup_(table_unrefp) Table *table = NULL;
|
||||
bool draw_separator = false;
|
||||
int ret = 0, r;
|
||||
|
||||
if (arg_output < 0)
|
||||
arg_output = argc > 1 ? OUTPUT_FRIENDLY : OUTPUT_TABLE;
|
||||
|
||||
if (arg_output == OUTPUT_TABLE) {
|
||||
table = table_new("name", "disposition", "uid", "gid", "realname", "home", "shell", "disposition-numeric");
|
||||
if (!table)
|
||||
return log_oom();
|
||||
|
||||
(void) table_set_align_percent(table, table_get_cell(table, 0, 2), 100);
|
||||
(void) table_set_align_percent(table, table_get_cell(table, 0, 3), 100);
|
||||
(void) table_set_empty_string(table, "-");
|
||||
(void) table_set_sort(table, 7, 2, (size_t) -1);
|
||||
(void) table_set_display(table, 0, 1, 2, 3, 4, 5, 6, (size_t) -1);
|
||||
}
|
||||
|
||||
if (argc > 1) {
|
||||
char **i;
|
||||
|
||||
STRV_FOREACH(i, argv + 1) {
|
||||
_cleanup_(user_record_unrefp) UserRecord *ur = NULL;
|
||||
uid_t uid;
|
||||
|
||||
if (parse_uid(*i, &uid) >= 0)
|
||||
r = userdb_by_uid(uid, arg_userdb_flags, &ur);
|
||||
else
|
||||
r = userdb_by_name(*i, arg_userdb_flags, &ur);
|
||||
if (r < 0) {
|
||||
if (r == -ESRCH)
|
||||
log_error_errno(r, "User %s does not exist.", *i);
|
||||
else if (r == -EHOSTDOWN)
|
||||
log_error_errno(r, "Selected user database service is not available for this request.");
|
||||
else
|
||||
log_error_errno(r, "Failed to find user %s: %m", *i);
|
||||
|
||||
if (ret >= 0)
|
||||
ret = r;
|
||||
} else {
|
||||
if (draw_separator && arg_output == OUTPUT_FRIENDLY)
|
||||
putchar('\n');
|
||||
|
||||
r = show_user(ur, table);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
draw_separator = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
|
||||
|
||||
r = userdb_all(arg_userdb_flags, &iterator);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to enumerate users: %m");
|
||||
|
||||
for (;;) {
|
||||
_cleanup_(user_record_unrefp) UserRecord *ur = NULL;
|
||||
|
||||
r = userdb_iterator_get(iterator, &ur);
|
||||
if (r == -ESRCH)
|
||||
break;
|
||||
if (r == -EHOSTDOWN)
|
||||
return log_error_errno(r, "Selected user database service is not available for this request.");
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed acquire next user: %m");
|
||||
|
||||
if (draw_separator && arg_output == OUTPUT_FRIENDLY)
|
||||
putchar('\n');
|
||||
|
||||
r = show_user(ur, table);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
draw_separator = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (table) {
|
||||
r = table_print(table, NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to show table: %m");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int show_group(GroupRecord *gr, Table *table) {
|
||||
int r;
|
||||
|
||||
assert(gr);
|
||||
|
||||
switch (arg_output) {
|
||||
|
||||
case OUTPUT_CLASSIC: {
|
||||
_cleanup_free_ char *m = NULL;
|
||||
|
||||
if (!gid_is_valid(gr->gid))
|
||||
break;
|
||||
|
||||
m = strv_join(gr->members, ",");
|
||||
if (!m)
|
||||
return log_oom();
|
||||
|
||||
printf("%s:x:" GID_FMT ":%s\n",
|
||||
gr->group_name,
|
||||
gr->gid,
|
||||
m);
|
||||
break;
|
||||
}
|
||||
|
||||
case OUTPUT_JSON:
|
||||
json_variant_dump(gr->json, JSON_FORMAT_COLOR_AUTO|JSON_FORMAT_PRETTY, NULL, 0);
|
||||
break;
|
||||
|
||||
case OUTPUT_FRIENDLY:
|
||||
group_record_show(gr, true);
|
||||
|
||||
if (gr->incomplete) {
|
||||
fflush(stdout);
|
||||
log_warning("Warning: lacking rights to acquire privileged fields of group record of '%s', output incomplete.", gr->group_name);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case OUTPUT_TABLE:
|
||||
assert(table);
|
||||
|
||||
r = table_add_many(
|
||||
table,
|
||||
TABLE_STRING, gr->group_name,
|
||||
TABLE_STRING, user_disposition_to_string(group_record_disposition(gr)),
|
||||
TABLE_GID, gr->gid,
|
||||
TABLE_INT, (int) group_record_disposition(gr));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to add row to table: %m");
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
assert_not_reached("Unexpected disply mode");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int display_group(int argc, char *argv[], void *userdata) {
|
||||
_cleanup_(table_unrefp) Table *table = NULL;
|
||||
bool draw_separator = false;
|
||||
int ret = 0, r;
|
||||
|
||||
if (arg_output < 0)
|
||||
arg_output = argc > 1 ? OUTPUT_FRIENDLY : OUTPUT_TABLE;
|
||||
|
||||
if (arg_output == OUTPUT_TABLE) {
|
||||
table = table_new("name", "disposition", "gid", "disposition-numeric");
|
||||
if (!table)
|
||||
return log_oom();
|
||||
|
||||
(void) table_set_align_percent(table, table_get_cell(table, 0, 2), 100);
|
||||
(void) table_set_sort(table, 3, 2, (size_t) -1);
|
||||
(void) table_set_display(table, 0, 1, 2, (size_t) -1);
|
||||
}
|
||||
|
||||
if (argc > 1) {
|
||||
char **i;
|
||||
|
||||
STRV_FOREACH(i, argv + 1) {
|
||||
_cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
|
||||
gid_t gid;
|
||||
|
||||
if (parse_gid(*i, &gid) >= 0)
|
||||
r = groupdb_by_gid(gid, arg_userdb_flags, &gr);
|
||||
else
|
||||
r = groupdb_by_name(*i, arg_userdb_flags, &gr);
|
||||
if (r < 0) {
|
||||
if (r == -ESRCH)
|
||||
log_error_errno(r, "Group %s does not exist.", *i);
|
||||
else if (r == -EHOSTDOWN)
|
||||
log_error_errno(r, "Selected group database service is not available for this request.");
|
||||
else
|
||||
log_error_errno(r, "Failed to find group %s: %m", *i);
|
||||
|
||||
if (ret >= 0)
|
||||
ret = r;
|
||||
} else {
|
||||
if (draw_separator && arg_output == OUTPUT_FRIENDLY)
|
||||
putchar('\n');
|
||||
|
||||
r = show_group(gr, table);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
draw_separator = true;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
|
||||
|
||||
r = groupdb_all(arg_userdb_flags, &iterator);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to enumerate groups: %m");
|
||||
|
||||
for (;;) {
|
||||
_cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
|
||||
|
||||
r = groupdb_iterator_get(iterator, &gr);
|
||||
if (r == -ESRCH)
|
||||
break;
|
||||
if (r == -EHOSTDOWN)
|
||||
return log_error_errno(r, "Selected group database service is not available for this request.");
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed acquire next group: %m");
|
||||
|
||||
if (draw_separator && arg_output == OUTPUT_FRIENDLY)
|
||||
putchar('\n');
|
||||
|
||||
r = show_group(gr, table);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
draw_separator = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (table) {
|
||||
r = table_print(table, NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to show table: %m");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int show_membership(const char *user, const char *group, Table *table) {
|
||||
int r;
|
||||
|
||||
assert(user);
|
||||
assert(group);
|
||||
|
||||
switch (arg_output) {
|
||||
|
||||
case OUTPUT_CLASSIC:
|
||||
/* Strictly speaking there's no 'classic' output for this concept, but let's output it in
|
||||
* similar style to the classic output for user/group info */
|
||||
|
||||
printf("%s:%s\n", user, group);
|
||||
break;
|
||||
|
||||
case OUTPUT_JSON: {
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
|
||||
|
||||
r = json_build(&v, JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR("user", JSON_BUILD_STRING(user)),
|
||||
JSON_BUILD_PAIR("group", JSON_BUILD_STRING(group))));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to build JSON object: %m");
|
||||
|
||||
json_variant_dump(v, JSON_FORMAT_PRETTY|JSON_FORMAT_COLOR_AUTO, NULL, NULL);
|
||||
break;
|
||||
}
|
||||
|
||||
case OUTPUT_FRIENDLY:
|
||||
/* Hmm, this is not particularly friendly, but not sure how we could do this better */
|
||||
printf("%s: %s\n", group, user);
|
||||
break;
|
||||
|
||||
case OUTPUT_TABLE:
|
||||
assert(table);
|
||||
|
||||
r = table_add_many(
|
||||
table,
|
||||
TABLE_STRING, user,
|
||||
TABLE_STRING, group);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to add row to table: %m");
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
assert_not_reached("Unexpected output mode");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int display_memberships(int argc, char *argv[], void *userdata) {
|
||||
_cleanup_(table_unrefp) Table *table = NULL;
|
||||
int ret = 0, r;
|
||||
|
||||
if (arg_output < 0)
|
||||
arg_output = OUTPUT_TABLE;
|
||||
|
||||
if (arg_output == OUTPUT_TABLE) {
|
||||
table = table_new("user", "group");
|
||||
if (!table)
|
||||
return log_oom();
|
||||
|
||||
(void) table_set_sort(table, 0, 1, (size_t) -1);
|
||||
}
|
||||
|
||||
if (argc > 1) {
|
||||
char **i;
|
||||
|
||||
STRV_FOREACH(i, argv + 1) {
|
||||
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
|
||||
|
||||
if (streq(argv[0], "users-in-group")) {
|
||||
r = membershipdb_by_group(*i, arg_userdb_flags, &iterator);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to enumerate users in group: %m");
|
||||
} else if (streq(argv[0], "groups-of-user")) {
|
||||
r = membershipdb_by_user(*i, arg_userdb_flags, &iterator);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to enumerate groups of user: %m");
|
||||
} else
|
||||
assert_not_reached("Unexpected verb");
|
||||
|
||||
for (;;) {
|
||||
_cleanup_free_ char *user = NULL, *group = NULL;
|
||||
|
||||
r = membershipdb_iterator_get(iterator, &user, &group);
|
||||
if (r == -ESRCH)
|
||||
break;
|
||||
if (r == -EHOSTDOWN)
|
||||
return log_error_errno(r, "Selected membership database service is not available for this request.");
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed acquire next membership: %m");
|
||||
|
||||
r = show_membership(user, group, table);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
|
||||
|
||||
r = membershipdb_all(arg_userdb_flags, &iterator);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to enumerate memberships: %m");
|
||||
|
||||
for (;;) {
|
||||
_cleanup_free_ char *user = NULL, *group = NULL;
|
||||
|
||||
r = membershipdb_iterator_get(iterator, &user, &group);
|
||||
if (r == -ESRCH)
|
||||
break;
|
||||
if (r == -EHOSTDOWN)
|
||||
return log_error_errno(r, "Selected membership database service is not available for this request.");
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed acquire next membership: %m");
|
||||
|
||||
r = show_membership(user, group, table);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
if (table) {
|
||||
r = table_print(table, NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to show table: %m");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int display_services(int argc, char *argv[], void *userdata) {
|
||||
_cleanup_(table_unrefp) Table *t = NULL;
|
||||
_cleanup_(closedirp) DIR *d = NULL;
|
||||
struct dirent *de;
|
||||
int r;
|
||||
|
||||
d = opendir("/run/systemd/userdb/");
|
||||
if (!d) {
|
||||
if (errno == ENOENT) {
|
||||
log_info("No services.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return log_error_errno(errno, "Failed to open /run/systemd/userdb/: %m");
|
||||
}
|
||||
|
||||
t = table_new("service", "listening");
|
||||
if (!t)
|
||||
return log_oom();
|
||||
|
||||
(void) table_set_sort(t, 0, (size_t) -1);
|
||||
|
||||
FOREACH_DIRENT(de, d, return -errno) {
|
||||
_cleanup_free_ char *j = NULL, *no = NULL;
|
||||
union sockaddr_union sockaddr;
|
||||
_cleanup_close_ int fd = -1;
|
||||
|
||||
j = path_join("/run/systemd/userdb/", de->d_name);
|
||||
if (!j)
|
||||
return log_oom();
|
||||
|
||||
r = sockaddr_un_set_path(&sockaddr.un, j);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Path %s does not fit in AF_UNIX socket address: %m", j);
|
||||
|
||||
fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
|
||||
if (fd < 0)
|
||||
return log_error_errno(r, "Failed to allocate AF_UNIX/SOCK_STREAM socket: %m");
|
||||
|
||||
if (connect(fd, &sockaddr.un, SOCKADDR_UN_LEN(sockaddr.un)) < 0) {
|
||||
no = strjoin("No (", errno_to_name(errno), ")");
|
||||
if (!no)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
r = table_add_many(t,
|
||||
TABLE_STRING, de->d_name,
|
||||
TABLE_STRING, no ?: "yes",
|
||||
TABLE_SET_COLOR, no ? ansi_highlight_red() : ansi_highlight_green());
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to add table row: %m");
|
||||
}
|
||||
|
||||
if (table_get_rows(t) <= 0) {
|
||||
log_info("No services.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (arg_output == OUTPUT_JSON)
|
||||
table_print_json(t, NULL, JSON_FORMAT_PRETTY|JSON_FORMAT_COLOR_AUTO);
|
||||
else
|
||||
table_print(t, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ssh_authorized_keys(int argc, char *argv[], void *userdata) {
|
||||
_cleanup_(user_record_unrefp) UserRecord *ur = NULL;
|
||||
int r;
|
||||
|
||||
if (!valid_user_group_name(argv[1]))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid user name '%s'.", argv[1]);
|
||||
|
||||
r = userdb_by_name(argv[1], arg_userdb_flags, &ur);
|
||||
if (r == -ESRCH)
|
||||
log_error_errno(r, "User %s does not exist.", argv[1]);
|
||||
else if (r == -EHOSTDOWN)
|
||||
log_error_errno(r, "Selected user database service is not available for this request.");
|
||||
else if (r < 0)
|
||||
log_error_errno(r, "Failed to find user %s: %m", argv[1]);
|
||||
|
||||
if (strv_isempty(ur->ssh_authorized_keys))
|
||||
log_debug("User record for %s has no public SSH keys.", argv[1]);
|
||||
else {
|
||||
char **i;
|
||||
|
||||
STRV_FOREACH(i, ur->ssh_authorized_keys)
|
||||
printf("%s\n", *i);
|
||||
}
|
||||
|
||||
if (ur->incomplete) {
|
||||
fflush(stdout);
|
||||
log_warning("Warning: lacking rights to acquire privileged fields of user record of '%s', output incomplete.", ur->user_name);
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
static int help(int argc, char *argv[], void *userdata) {
|
||||
_cleanup_free_ char *link = NULL;
|
||||
int r;
|
||||
|
||||
(void) pager_open(arg_pager_flags);
|
||||
|
||||
r = terminal_urlify_man("userdbctl", "1", &link);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
printf("%s [OPTIONS...] COMMAND ...\n\n"
|
||||
"%sShow user and group information.%s\n"
|
||||
"\nCommands:\n"
|
||||
" user [USER…] Inspect user\n"
|
||||
" group [GROUP…] Inspect group\n"
|
||||
" users-in-group [GROUP…] Show users that are members of specified group(s)\n"
|
||||
" groups-of-user [USER…] Show groups the specified user(s) is a member of\n"
|
||||
" services Show enabled database services\n"
|
||||
"\nOptions:\n"
|
||||
" -h --help Show this help\n"
|
||||
" --version Show package version\n"
|
||||
" --no-pager Do not pipe output into a pager\n"
|
||||
" --no-legend Do not show the headers and footers\n"
|
||||
" --output=MODE Select output mode (classic, friendly, table, json)\n"
|
||||
" -j Equivalent to --output=json\n"
|
||||
" -s --service=SERVICE[:SERVICE…]\n"
|
||||
" Query the specified service\n"
|
||||
" --with-nss=BOOL Control whether to include glibc NSS data\n"
|
||||
" -N Disable inclusion of glibc NSS data and disable synthesizing\n"
|
||||
" (Same as --with-nss=no --synthesize=no)\n"
|
||||
" --synthesize=BOOL Synthesize root/nobody user\n"
|
||||
"\nSee the %s for details.\n"
|
||||
, program_invocation_short_name
|
||||
, ansi_highlight(), ansi_normal()
|
||||
, link
|
||||
);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_argv(int argc, char *argv[]) {
|
||||
|
||||
enum {
|
||||
ARG_VERSION = 0x100,
|
||||
ARG_NO_PAGER,
|
||||
ARG_NO_LEGEND,
|
||||
ARG_OUTPUT,
|
||||
ARG_WITH_NSS,
|
||||
ARG_SYNTHESIZE,
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "version", no_argument, NULL, ARG_VERSION },
|
||||
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
|
||||
{ "no-legend", no_argument, NULL, ARG_NO_LEGEND },
|
||||
{ "output", required_argument, NULL, ARG_OUTPUT },
|
||||
{ "service", required_argument, NULL, 's' },
|
||||
{ "with-nss", required_argument, NULL, ARG_WITH_NSS },
|
||||
{ "synthesize", required_argument, NULL, ARG_SYNTHESIZE },
|
||||
{}
|
||||
};
|
||||
|
||||
const char *e;
|
||||
int r;
|
||||
|
||||
assert(argc >= 0);
|
||||
assert(argv);
|
||||
|
||||
/* We are going to update this environment variable with our own, hence let's first read what is already set */
|
||||
e = getenv("SYSTEMD_ONLY_USERDB");
|
||||
if (e) {
|
||||
char **l;
|
||||
|
||||
l = strv_split(e, ":");
|
||||
if (!l)
|
||||
return log_oom();
|
||||
|
||||
strv_free(arg_services);
|
||||
arg_services = l;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
int c;
|
||||
|
||||
c = getopt_long(argc, argv, "hjs:N", options, NULL);
|
||||
if (c < 0)
|
||||
break;
|
||||
|
||||
switch (c) {
|
||||
|
||||
case 'h':
|
||||
return help(0, NULL, NULL);
|
||||
|
||||
case ARG_VERSION:
|
||||
return version();
|
||||
|
||||
case ARG_NO_PAGER:
|
||||
arg_pager_flags |= PAGER_DISABLE;
|
||||
break;
|
||||
|
||||
case ARG_NO_LEGEND:
|
||||
arg_legend = false;
|
||||
break;
|
||||
|
||||
case ARG_OUTPUT:
|
||||
if (streq(optarg, "classic"))
|
||||
arg_output = OUTPUT_CLASSIC;
|
||||
else if (streq(optarg, "friendly"))
|
||||
arg_output = OUTPUT_FRIENDLY;
|
||||
else if (streq(optarg, "json"))
|
||||
arg_output = OUTPUT_JSON;
|
||||
else if (streq(optarg, "table"))
|
||||
arg_output = OUTPUT_TABLE;
|
||||
else if (streq(optarg, "help")) {
|
||||
puts("classic\n"
|
||||
"friendly\n"
|
||||
"json");
|
||||
return 0;
|
||||
} else
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid --output= mode: %s", optarg);
|
||||
|
||||
break;
|
||||
|
||||
case 'j':
|
||||
arg_output = OUTPUT_JSON;
|
||||
break;
|
||||
|
||||
case 's':
|
||||
if (isempty(optarg))
|
||||
arg_services = strv_free(arg_services);
|
||||
else {
|
||||
char **l;
|
||||
|
||||
l = strv_split(optarg, ":");
|
||||
if (!l)
|
||||
return log_oom();
|
||||
|
||||
r = strv_extend_strv(&arg_services, l, true);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'N':
|
||||
arg_userdb_flags |= USERDB_AVOID_NSS|USERDB_DONT_SYNTHESIZE;
|
||||
break;
|
||||
|
||||
case ARG_WITH_NSS:
|
||||
r = parse_boolean(optarg);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse --with-nss= parameter: %s", optarg);
|
||||
|
||||
SET_FLAG(arg_userdb_flags, USERDB_AVOID_NSS, !r);
|
||||
break;
|
||||
|
||||
case ARG_SYNTHESIZE:
|
||||
r = parse_boolean(optarg);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse --synthesize= parameter: %s", optarg);
|
||||
|
||||
SET_FLAG(arg_userdb_flags, USERDB_DONT_SYNTHESIZE, !r);
|
||||
break;
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
|
||||
default:
|
||||
assert_not_reached("Unhandled option");
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int run(int argc, char *argv[]) {
|
||||
static const Verb verbs[] = {
|
||||
{ "help", VERB_ANY, VERB_ANY, 0, help },
|
||||
{ "user", VERB_ANY, VERB_ANY, VERB_DEFAULT, display_user },
|
||||
{ "group", VERB_ANY, VERB_ANY, 0, display_group },
|
||||
{ "users-in-group", VERB_ANY, VERB_ANY, 0, display_memberships },
|
||||
{ "groups-of-user", VERB_ANY, VERB_ANY, 0, display_memberships },
|
||||
{ "services", VERB_ANY, 1, 0, display_services },
|
||||
|
||||
/* This one is a helper for sshd_config's AuthorizedKeysCommand= setting, it's not a
|
||||
* user-facing verb and thus should not appear in man pages or --help texts. */
|
||||
{ "ssh-authorized-keys", 2, 2, 0, ssh_authorized_keys },
|
||||
{}
|
||||
};
|
||||
|
||||
int r;
|
||||
|
||||
log_show_color(true);
|
||||
log_parse_environment();
|
||||
log_open();
|
||||
|
||||
r = parse_argv(argc, argv);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
if (arg_services) {
|
||||
_cleanup_free_ char *e = NULL;
|
||||
|
||||
e = strv_join(arg_services, ":");
|
||||
if (!e)
|
||||
return log_oom();
|
||||
|
||||
if (setenv("SYSTEMD_ONLY_USERDB", e, true) < 0)
|
||||
return log_error_errno(r, "Failed to set $SYSTEMD_ONLY_USERDB: %m");
|
||||
|
||||
log_info("Enabled services: %s", e);
|
||||
} else {
|
||||
if (unsetenv("SYSTEMD_ONLY_USERDB") < 0)
|
||||
return log_error_errno(r, "Failed to unset $SYSTEMD_ONLY_USERDB: %m");
|
||||
}
|
||||
|
||||
return dispatch_verb(argc, argv, verbs, NULL);
|
||||
}
|
||||
|
||||
DEFINE_MAIN_FUNCTION(run);
|
302
src/userdb/userdbd-manager.c
Normal file
302
src/userdb/userdbd-manager.c
Normal file
@ -0,0 +1,302 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include "sd-daemon.h"
|
||||
|
||||
#include "fd-util.h"
|
||||
#include "fs-util.h"
|
||||
#include "mkdir.h"
|
||||
#include "process-util.h"
|
||||
#include "set.h"
|
||||
#include "signal-util.h"
|
||||
#include "socket-util.h"
|
||||
#include "stdio-util.h"
|
||||
#include "umask-util.h"
|
||||
#include "userdbd-manager.h"
|
||||
|
||||
#define LISTEN_TIMEOUT_USEC (25 * USEC_PER_SEC)
|
||||
|
||||
static int start_workers(Manager *m, bool explicit_request);
|
||||
|
||||
static int on_sigchld(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {
|
||||
Manager *m = userdata;
|
||||
|
||||
assert(s);
|
||||
assert(m);
|
||||
|
||||
for (;;) {
|
||||
siginfo_t siginfo = {};
|
||||
bool removed = false;
|
||||
|
||||
if (waitid(P_ALL, 0, &siginfo, WNOHANG|WEXITED) < 0) {
|
||||
if (errno == ECHILD)
|
||||
break;
|
||||
|
||||
log_warning_errno(errno, "Failed to invoke waitid(): %m");
|
||||
break;
|
||||
}
|
||||
if (siginfo.si_pid == 0)
|
||||
break;
|
||||
|
||||
if (set_remove(m->workers_dynamic, PID_TO_PTR(siginfo.si_pid)))
|
||||
removed = true;
|
||||
if (set_remove(m->workers_fixed, PID_TO_PTR(siginfo.si_pid)))
|
||||
removed = true;
|
||||
|
||||
if (!removed) {
|
||||
log_warning("Weird, got SIGCHLD for unknown child " PID_FMT ", ignoring.", siginfo.si_pid);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (siginfo.si_code == CLD_EXITED) {
|
||||
if (siginfo.si_status == EXIT_SUCCESS)
|
||||
log_debug("Worker " PID_FMT " exited successfully.", siginfo.si_pid);
|
||||
else
|
||||
log_warning("Worker " PID_FMT " died with a failure exit status %i, ignoring.", siginfo.si_pid, siginfo.si_status);
|
||||
} else if (siginfo.si_code == CLD_KILLED)
|
||||
log_warning("Worker " PID_FMT " was killed by signal %s, ignoring.", siginfo.si_pid, signal_to_string(siginfo.si_status));
|
||||
else if (siginfo.si_code == CLD_DUMPED)
|
||||
log_warning("Worker " PID_FMT " dumped core by signal %s, ignoring.", siginfo.si_pid, signal_to_string(siginfo.si_status));
|
||||
else
|
||||
log_warning("Can't handle SIGCHLD of this type");
|
||||
}
|
||||
|
||||
(void) start_workers(m, false); /* Fill up workers again if we fell below the low watermark */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int on_sigusr2(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {
|
||||
Manager *m = userdata;
|
||||
|
||||
assert(s);
|
||||
assert(m);
|
||||
|
||||
(void) start_workers(m, true); /* Workers told us there's more work, let's add one more worker as long as we are below the high watermark */
|
||||
return 0;
|
||||
}
|
||||
|
||||
int manager_new(Manager **ret) {
|
||||
Manager *m;
|
||||
int r;
|
||||
|
||||
m = new(Manager, 1);
|
||||
if (!m)
|
||||
return -ENOMEM;
|
||||
|
||||
*m = (Manager) {
|
||||
.listen_fd = -1,
|
||||
.worker_ratelimit = {
|
||||
.interval = 5 * USEC_PER_SEC,
|
||||
.burst = 50,
|
||||
},
|
||||
};
|
||||
|
||||
r = sd_event_new(&m->event);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
(void) sd_event_set_watchdog(m->event, true);
|
||||
|
||||
m->workers_fixed = set_new(NULL);
|
||||
m->workers_dynamic = set_new(NULL);
|
||||
|
||||
if (!m->workers_fixed || !m->workers_dynamic)
|
||||
return -ENOMEM;
|
||||
|
||||
r = sd_event_add_signal(m->event, &m->sigusr2_event_source, SIGUSR2, on_sigusr2, m);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_event_add_signal(m->event, &m->sigchld_event_source, SIGCHLD, on_sigchld, m);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
*ret = TAKE_PTR(m);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Manager* manager_free(Manager *m) {
|
||||
if (!m)
|
||||
return NULL;
|
||||
|
||||
set_free(m->workers_fixed);
|
||||
set_free(m->workers_dynamic);
|
||||
|
||||
sd_event_source_disable_unref(m->sigusr2_event_source);
|
||||
sd_event_source_disable_unref(m->sigchld_event_source);
|
||||
|
||||
sd_event_unref(m->event);
|
||||
|
||||
return mfree(m);
|
||||
}
|
||||
|
||||
static size_t manager_current_workers(Manager *m) {
|
||||
assert(m);
|
||||
|
||||
return set_size(m->workers_fixed) + set_size(m->workers_dynamic);
|
||||
}
|
||||
|
||||
static int start_one_worker(Manager *m) {
|
||||
bool fixed;
|
||||
pid_t pid;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
|
||||
fixed = set_size(m->workers_fixed) < USERDB_WORKERS_MIN;
|
||||
|
||||
r = safe_fork("(sd-worker)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG, &pid);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to fork new worker child: %m");
|
||||
if (r == 0) {
|
||||
char pids[DECIMAL_STR_MAX(pid_t)];
|
||||
/* Child */
|
||||
|
||||
log_close();
|
||||
|
||||
r = close_all_fds(&m->listen_fd, 1);
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to close fds in child: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
log_open();
|
||||
|
||||
if (m->listen_fd == 3) {
|
||||
r = fd_cloexec(3, false);
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to turn off O_CLOEXEC for fd 3: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
} else {
|
||||
if (dup2(m->listen_fd, 3) < 0) { /* dup2() creates with O_CLOEXEC off */
|
||||
log_error_errno(errno, "Failed to move listen fd to 3: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
safe_close(m->listen_fd);
|
||||
}
|
||||
|
||||
xsprintf(pids, PID_FMT, pid);
|
||||
if (setenv("LISTEN_PID", pids, 1) < 0) {
|
||||
log_error_errno(errno, "Failed to set $LISTEN_PID: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (setenv("LISTEN_FDS", "1", 1) < 0) {
|
||||
log_error_errno(errno, "Failed to set $LISTEN_FDS: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
|
||||
if (setenv("USERDB_FIXED_WORKER", one_zero(fixed), 1) < 0) {
|
||||
log_error_errno(errno, "Failed to set $USERDB_FIXED_WORKER: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* execl("/home/lennart/projects/systemd/build/systemd-userwork", "systemd-userwork", "xxxxxxxxxxxxxxxx", NULL); /\* With some extra space rename_process() can make use of *\/ */
|
||||
/* execl("/usr/bin/valgrind", "valgrind", "/home/lennart/projects/systemd/build/systemd-userwork", "systemd-userwork", "xxxxxxxxxxxxxxxx", NULL); /\* With some extra space rename_process() can make use of *\/ */
|
||||
|
||||
execl(SYSTEMD_USERWORK_PATH, "systemd-userwork", "xxxxxxxxxxxxxxxx", NULL); /* With some extra space rename_process() can make use of */
|
||||
log_error_errno(errno, "Failed start worker process: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (fixed)
|
||||
r = set_put(m->workers_fixed, PID_TO_PTR(pid));
|
||||
else
|
||||
r = set_put(m->workers_dynamic, PID_TO_PTR(pid));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to add child process to set: %m");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int start_workers(Manager *m, bool explicit_request) {
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
|
||||
for (;;) {
|
||||
size_t n;
|
||||
|
||||
n = manager_current_workers(m);
|
||||
if (n >= USERDB_WORKERS_MIN && (!explicit_request || n >= USERDB_WORKERS_MAX))
|
||||
break;
|
||||
|
||||
if (!ratelimit_below(&m->worker_ratelimit)) {
|
||||
/* If we keep starting workers too often, let's fail the whole daemon, something is wrong */
|
||||
sd_event_exit(m->event, EXIT_FAILURE);
|
||||
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), "Worker threads requested too frequently, something is wrong.");
|
||||
}
|
||||
|
||||
r = start_one_worker(m);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
explicit_request = false;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int manager_startup(Manager *m) {
|
||||
struct timeval ts;
|
||||
int n, r;
|
||||
|
||||
assert(m);
|
||||
assert(m->listen_fd < 0);
|
||||
|
||||
n = sd_listen_fds(false);
|
||||
if (n < 0)
|
||||
return log_error_errno(n, "Failed to determine number of passed file descriptors: %m");
|
||||
if (n > 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected one listening fd, got %i.", n);
|
||||
if (n == 1)
|
||||
m->listen_fd = SD_LISTEN_FDS_START;
|
||||
else {
|
||||
union sockaddr_union sockaddr;
|
||||
|
||||
r = sockaddr_un_set_path(&sockaddr.un, "/run/systemd/userdb/io.systemd.NameServiceSwitch");
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Cannot assign socket path to socket address: %m");
|
||||
|
||||
r = mkdir_p("/run/systemd/userdb", 0755);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to create /run/systemd/userdb: %m");
|
||||
|
||||
m->listen_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
|
||||
if (m->listen_fd < 0)
|
||||
return log_error_errno(errno, "Failed to bind on socket: %m");
|
||||
|
||||
(void) sockaddr_un_unlink(&sockaddr.un);
|
||||
|
||||
RUN_WITH_UMASK(0000)
|
||||
if (bind(m->listen_fd, &sockaddr.sa, SOCKADDR_UN_LEN(sockaddr.un)) < 0)
|
||||
return log_error_errno(errno, "Failed to bind socket: %m");
|
||||
|
||||
r = symlink_idempotent("io.systemd.NameServiceSwitch", "/run/systemd/userdb/io.systemd.Multiplexer", false);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to bind io.systemd.Multiplexer: %m");
|
||||
|
||||
if (listen(m->listen_fd, SOMAXCONN) < 0)
|
||||
return log_error_errno(errno, "Failed to listen on socket: %m");
|
||||
}
|
||||
|
||||
/* Let's make sure every accept() call on this socket times out after 25s. This allows workers to be
|
||||
* GC'ed on idle */
|
||||
if (setsockopt(m->listen_fd, SOL_SOCKET, SO_RCVTIMEO, timeval_store(&ts, LISTEN_TIMEOUT_USEC), sizeof(ts)) < 0)
|
||||
return log_error_errno(errno, "Failed to se SO_RCVTIMEO: %m");
|
||||
|
||||
return start_workers(m, false);
|
||||
}
|
34
src/userdb/userdbd-manager.h
Normal file
34
src/userdb/userdbd-manager.h
Normal file
@ -0,0 +1,34 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#include "sd-bus.h"
|
||||
#include "sd-event.h"
|
||||
|
||||
typedef struct Manager Manager;
|
||||
|
||||
#include "hashmap.h"
|
||||
#include "varlink.h"
|
||||
#include "ratelimit.h"
|
||||
|
||||
#define USERDB_WORKERS_MIN 3
|
||||
#define USERDB_WORKERS_MAX 4096
|
||||
|
||||
struct Manager {
|
||||
sd_event *event;
|
||||
|
||||
Set *workers_fixed; /* Workers 0…USERDB_WORKERS_MIN */
|
||||
Set *workers_dynamic; /* Workers USERD_WORKERS_MIN+1…USERDB_WORKERS_MAX */
|
||||
|
||||
sd_event_source *sigusr2_event_source;
|
||||
sd_event_source *sigchld_event_source;
|
||||
|
||||
int listen_fd;
|
||||
|
||||
RateLimit worker_ratelimit;
|
||||
};
|
||||
|
||||
int manager_new(Manager **ret);
|
||||
Manager* manager_free(Manager *m);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
|
||||
|
||||
int manager_startup(Manager *m);
|
56
src/userdb/userdbd.c
Normal file
56
src/userdb/userdbd.c
Normal file
@ -0,0 +1,56 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "daemon-util.h"
|
||||
#include "userdbd-manager.h"
|
||||
#include "log.h"
|
||||
#include "main-func.h"
|
||||
#include "signal-util.h"
|
||||
|
||||
/* 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
|
||||
* 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
|
||||
* socket in /run/systemd/userdb/. It's supposed to simplify clients that don't want to implement
|
||||
* the full iterative logic on their own.
|
||||
*/
|
||||
|
||||
static int run(int argc, char *argv[]) {
|
||||
_cleanup_(notify_on_cleanup) const char *notify_stop = NULL;
|
||||
_cleanup_(manager_freep) Manager *m = NULL;
|
||||
int r;
|
||||
|
||||
log_setup_service();
|
||||
|
||||
umask(0022);
|
||||
|
||||
if (argc != 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments.");
|
||||
|
||||
if (setenv("SYSTEMD_BYPASS_USERDB", "io.systemd.NameServiceSwitch:io.systemd.Multiplexer", 1) < 0)
|
||||
return log_error_errno(errno, "Failed to se $SYSTEMD_BYPASS_USERDB: %m");
|
||||
|
||||
assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, SIGTERM, SIGINT, SIGUSR2, -1) >= 0);
|
||||
|
||||
r = manager_new(&m);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Could not create manager: %m");
|
||||
|
||||
r = manager_startup(m);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to start up daemon: %m");
|
||||
|
||||
notify_stop = notify_start(NOTIFY_READY, NOTIFY_STOPPING);
|
||||
|
||||
r = sd_event_loop(m->event);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Event loop failed: %m");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_MAIN_FUNCTION(run);
|
778
src/userdb/userwork.c
Normal file
778
src/userdb/userwork.c
Normal file
@ -0,0 +1,778 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
|
||||
#include <poll.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include "sd-daemon.h"
|
||||
|
||||
#include "env-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "group-record-nss.h"
|
||||
#include "group-record.h"
|
||||
#include "main-func.h"
|
||||
#include "process-util.h"
|
||||
#include "strv.h"
|
||||
#include "time-util.h"
|
||||
#include "user-record-nss.h"
|
||||
#include "user-record.h"
|
||||
#include "user-util.h"
|
||||
#include "userdb.h"
|
||||
#include "varlink.h"
|
||||
|
||||
#define ITERATIONS_MAX 64U
|
||||
#define RUNTIME_MAX_USEC (5 * USEC_PER_MINUTE)
|
||||
#define PRESSURE_SLEEP_TIME_USEC (50 * USEC_PER_MSEC)
|
||||
#define CONNECTION_IDLE_USEC (15 * USEC_PER_SEC)
|
||||
#define LISTEN_IDLE_USEC (90 * USEC_PER_SEC)
|
||||
|
||||
typedef struct LookupParameters {
|
||||
const char *user_name;
|
||||
const char *group_name;
|
||||
union {
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
};
|
||||
const char *service;
|
||||
} LookupParameters;
|
||||
|
||||
static int add_nss_service(JsonVariant **v) {
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *status = NULL, *z = NULL;
|
||||
char buf[SD_ID128_STRING_MAX];
|
||||
sd_id128_t mid;
|
||||
int r;
|
||||
|
||||
assert(v);
|
||||
|
||||
/* Patch in service field if it's missing. The assumption here is that this field is unset only for
|
||||
* NSS records */
|
||||
|
||||
if (json_variant_by_key(*v, "service"))
|
||||
return 0;
|
||||
|
||||
r = sd_id128_get_machine(&mid);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
status = json_variant_ref(json_variant_by_key(*v, "status"));
|
||||
z = json_variant_ref(json_variant_by_key(status, sd_id128_to_string(mid, buf)));
|
||||
|
||||
if (json_variant_by_key(z, "service"))
|
||||
return 0;
|
||||
|
||||
r = json_variant_set_field_string(&z, "service", "io.systemd.NameServiceSwitch");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = json_variant_set_field(&status, buf, z);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return json_variant_set_field(v, "status", status);
|
||||
}
|
||||
|
||||
static int build_user_json(Varlink *link, UserRecord *ur, JsonVariant **ret) {
|
||||
_cleanup_(user_record_unrefp) UserRecord *stripped = NULL;
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
|
||||
UserRecordLoadFlags flags;
|
||||
uid_t peer_uid;
|
||||
bool trusted;
|
||||
int r;
|
||||
|
||||
assert(ur);
|
||||
assert(ret);
|
||||
|
||||
r = varlink_get_peer_uid(link, &peer_uid);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Unable to query peer UID, ignoring: %m");
|
||||
trusted = false;
|
||||
} else
|
||||
trusted = peer_uid == 0 || peer_uid == ur->uid;
|
||||
|
||||
flags = USER_RECORD_REQUIRE_REGULAR|USER_RECORD_ALLOW_PER_MACHINE|USER_RECORD_ALLOW_BINDING|USER_RECORD_STRIP_SECRET|USER_RECORD_ALLOW_STATUS|USER_RECORD_ALLOW_SIGNATURE;
|
||||
if (trusted)
|
||||
flags |= USER_RECORD_ALLOW_PRIVILEGED;
|
||||
else
|
||||
flags |= USER_RECORD_STRIP_PRIVILEGED;
|
||||
|
||||
r = user_record_clone(ur, flags, &stripped);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
stripped->incomplete =
|
||||
ur->incomplete ||
|
||||
(FLAGS_SET(ur->mask, USER_RECORD_PRIVILEGED) &&
|
||||
!FLAGS_SET(stripped->mask, USER_RECORD_PRIVILEGED));
|
||||
|
||||
v = json_variant_ref(stripped->json);
|
||||
r = add_nss_service(&v);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return json_build(ret, JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR("record", JSON_BUILD_VARIANT(v)),
|
||||
JSON_BUILD_PAIR("incomplete", JSON_BUILD_BOOLEAN(stripped->incomplete))));
|
||||
}
|
||||
|
||||
static int vl_method_get_user_record(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
|
||||
|
||||
static const JsonDispatch dispatch_table[] = {
|
||||
{ "uid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(LookupParameters, uid), 0 },
|
||||
{ "userName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, user_name), 0 },
|
||||
{ "service", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, service), 0 },
|
||||
{}
|
||||
};
|
||||
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
|
||||
_cleanup_(user_record_unrefp) UserRecord *hr = NULL;
|
||||
LookupParameters p = {
|
||||
.uid = UID_INVALID,
|
||||
};
|
||||
int r;
|
||||
|
||||
assert(parameters);
|
||||
|
||||
r = json_dispatch(parameters, dispatch_table, NULL, 0, &p);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (streq_ptr(p.service, "io.systemd.NameServiceSwitch")) {
|
||||
if (uid_is_valid(p.uid))
|
||||
r = nss_user_record_by_uid(p.uid, &hr);
|
||||
else if (p.user_name)
|
||||
r = nss_user_record_by_name(p.user_name, &hr);
|
||||
else {
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *last = NULL;
|
||||
|
||||
setpwent();
|
||||
|
||||
for (;;) {
|
||||
_cleanup_(user_record_unrefp) UserRecord *z = NULL;
|
||||
_cleanup_free_ char *sbuf = NULL;
|
||||
struct passwd *pw;
|
||||
struct spwd spwd;
|
||||
|
||||
errno = 0;
|
||||
pw = getpwent();
|
||||
if (!pw) {
|
||||
if (errno != 0)
|
||||
log_debug_errno(errno, "Failure while iterating through NSS user database, ignoring: %m");
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
r = nss_spwd_for_passwd(pw, &spwd, &sbuf);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to acquire shadow entry for user %s, ignoring: %m", pw->pw_name);
|
||||
|
||||
r = nss_passwd_to_user_record(pw, NULL, &z);
|
||||
if (r < 0) {
|
||||
endpwent();
|
||||
return r;
|
||||
}
|
||||
|
||||
if (last) {
|
||||
r = varlink_notify(link, last);
|
||||
if (r < 0) {
|
||||
endpwent();
|
||||
return r;
|
||||
}
|
||||
|
||||
last = json_variant_unref(last);
|
||||
}
|
||||
|
||||
r = build_user_json(link, z, &last);
|
||||
if (r < 0) {
|
||||
endpwent();
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
endpwent();
|
||||
|
||||
if (!last)
|
||||
return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
|
||||
|
||||
return varlink_reply(link, last);
|
||||
}
|
||||
|
||||
} else if (streq_ptr(p.service, "io.systemd.Multiplexer")) {
|
||||
|
||||
if (uid_is_valid(p.uid))
|
||||
r = userdb_by_uid(p.uid, USERDB_AVOID_MULTIPLEXER, &hr);
|
||||
else if (p.user_name)
|
||||
r = userdb_by_name(p.user_name, USERDB_AVOID_MULTIPLEXER, &hr);
|
||||
else {
|
||||
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *last = NULL;
|
||||
|
||||
r = userdb_all(USERDB_AVOID_MULTIPLEXER, &iterator);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
for (;;) {
|
||||
_cleanup_(user_record_unrefp) UserRecord *z = NULL;
|
||||
|
||||
r = userdb_iterator_get(iterator, &z);
|
||||
if (r == -ESRCH)
|
||||
break;
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (last) {
|
||||
r = varlink_notify(link, last);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
last = json_variant_unref(last);
|
||||
}
|
||||
|
||||
r = build_user_json(link, z, &last);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (!last)
|
||||
return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
|
||||
|
||||
return varlink_reply(link, last);
|
||||
}
|
||||
} else
|
||||
return varlink_error(link, "io.systemd.UserDatabase.BadService", NULL);
|
||||
if (r == -ESRCH)
|
||||
return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "User lookup failed abnormally: %m");
|
||||
return varlink_error(link, "io.systemd.UserDatabase.ServiceNotAvailable", NULL);
|
||||
}
|
||||
|
||||
if ((uid_is_valid(p.uid) && hr->uid != p.uid) ||
|
||||
(p.user_name && !streq(hr->user_name, p.user_name)))
|
||||
return varlink_error(link, "io.systemd.UserDatabase.ConflictingRecordFound", NULL);
|
||||
|
||||
r = build_user_json(link, hr, &v);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return varlink_reply(link, v);
|
||||
}
|
||||
|
||||
static int build_group_json(Varlink *link, GroupRecord *gr, JsonVariant **ret) {
|
||||
_cleanup_(group_record_unrefp) GroupRecord *stripped = NULL;
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
|
||||
UserRecordLoadFlags flags;
|
||||
uid_t peer_uid;
|
||||
bool trusted;
|
||||
int r;
|
||||
|
||||
assert(gr);
|
||||
assert(ret);
|
||||
|
||||
r = varlink_get_peer_uid(link, &peer_uid);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Unable to query peer UID, ignoring: %m");
|
||||
trusted = false;
|
||||
} else
|
||||
trusted = peer_uid == 0;
|
||||
|
||||
flags = USER_RECORD_REQUIRE_REGULAR|USER_RECORD_ALLOW_PER_MACHINE|USER_RECORD_ALLOW_BINDING|USER_RECORD_STRIP_SECRET|USER_RECORD_ALLOW_STATUS|USER_RECORD_ALLOW_SIGNATURE;
|
||||
if (trusted)
|
||||
flags |= USER_RECORD_ALLOW_PRIVILEGED;
|
||||
else
|
||||
flags |= USER_RECORD_STRIP_PRIVILEGED;
|
||||
|
||||
r = group_record_clone(gr, flags, &stripped);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
stripped->incomplete =
|
||||
gr->incomplete ||
|
||||
(FLAGS_SET(gr->mask, USER_RECORD_PRIVILEGED) &&
|
||||
!FLAGS_SET(stripped->mask, USER_RECORD_PRIVILEGED));
|
||||
|
||||
v = json_variant_ref(gr->json);
|
||||
r = add_nss_service(&v);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return json_build(ret, JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR("record", JSON_BUILD_VARIANT(v)),
|
||||
JSON_BUILD_PAIR("incomplete", JSON_BUILD_BOOLEAN(stripped->incomplete))));
|
||||
}
|
||||
|
||||
static int vl_method_get_group_record(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
|
||||
|
||||
static const JsonDispatch dispatch_table[] = {
|
||||
{ "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(LookupParameters, gid), 0 },
|
||||
{ "groupName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, group_name), 0 },
|
||||
{ "service", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, service), 0 },
|
||||
{}
|
||||
};
|
||||
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
|
||||
_cleanup_(group_record_unrefp) GroupRecord *g = NULL;
|
||||
LookupParameters p = {
|
||||
.gid = GID_INVALID,
|
||||
};
|
||||
int r;
|
||||
|
||||
assert(parameters);
|
||||
|
||||
r = json_dispatch(parameters, dispatch_table, NULL, 0, &p);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (streq_ptr(p.service, "io.systemd.NameServiceSwitch")) {
|
||||
|
||||
if (gid_is_valid(p.gid))
|
||||
r = nss_group_record_by_gid(p.gid, &g);
|
||||
else if (p.group_name)
|
||||
r = nss_group_record_by_name(p.group_name, &g);
|
||||
else {
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *last = NULL;
|
||||
|
||||
setgrent();
|
||||
|
||||
for (;;) {
|
||||
_cleanup_(group_record_unrefp) GroupRecord *z = NULL;
|
||||
_cleanup_free_ char *sbuf = NULL;
|
||||
struct group *grp;
|
||||
struct sgrp sgrp;
|
||||
|
||||
errno = 0;
|
||||
grp = getgrent();
|
||||
if (!grp) {
|
||||
if (errno != 0)
|
||||
log_debug_errno(errno, "Failure while iterating through NSS group database, ignoring: %m");
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
r = nss_sgrp_for_group(grp, &sgrp, &sbuf);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to acquire shadow entry for group %s, ignoring: %m", grp->gr_name);
|
||||
|
||||
r = nss_group_to_group_record(grp, r >= 0 ? &sgrp : NULL, &z);
|
||||
if (r < 0) {
|
||||
endgrent();
|
||||
return r;
|
||||
}
|
||||
|
||||
if (last) {
|
||||
r = varlink_notify(link, last);
|
||||
if (r < 0) {
|
||||
endgrent();
|
||||
return r;
|
||||
}
|
||||
|
||||
last = json_variant_unref(last);
|
||||
}
|
||||
|
||||
r = build_group_json(link, z, &last);
|
||||
if (r < 0) {
|
||||
endgrent();
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
endgrent();
|
||||
|
||||
if (!last)
|
||||
return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
|
||||
|
||||
return varlink_reply(link, last);
|
||||
}
|
||||
|
||||
} else if (streq_ptr(p.service, "io.systemd.Multiplexer")) {
|
||||
|
||||
if (gid_is_valid(p.gid))
|
||||
r = groupdb_by_gid(p.gid, USERDB_AVOID_MULTIPLEXER, &g);
|
||||
else if (p.group_name)
|
||||
r = groupdb_by_name(p.group_name, USERDB_AVOID_MULTIPLEXER, &g);
|
||||
else {
|
||||
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *last = NULL;
|
||||
|
||||
r = groupdb_all(USERDB_AVOID_MULTIPLEXER, &iterator);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
for (;;) {
|
||||
_cleanup_(group_record_unrefp) GroupRecord *z = NULL;
|
||||
|
||||
r = groupdb_iterator_get(iterator, &z);
|
||||
if (r == -ESRCH)
|
||||
break;
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (last) {
|
||||
r = varlink_notify(link, last);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
last = json_variant_unref(last);
|
||||
}
|
||||
|
||||
r = build_group_json(link, z, &last);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (!last)
|
||||
return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
|
||||
|
||||
return varlink_reply(link, last);
|
||||
}
|
||||
} else
|
||||
return varlink_error(link, "io.systemd.UserDatabase.BadService", NULL);
|
||||
if (r == -ESRCH)
|
||||
return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
|
||||
if (r < 0) {
|
||||
log_debug_errno(r, "Group lookup failed abnormally: %m");
|
||||
return varlink_error(link, "io.systemd.UserDatabase.ServiceNotAvailable", NULL);
|
||||
}
|
||||
|
||||
if ((uid_is_valid(p.gid) && g->gid != p.gid) ||
|
||||
(p.group_name && !streq(g->group_name, p.group_name)))
|
||||
return varlink_error(link, "io.systemd.UserDatabase.ConflictingRecordFound", NULL);
|
||||
|
||||
r = build_group_json(link, g, &v);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return varlink_reply(link, v);
|
||||
}
|
||||
|
||||
static int vl_method_get_memberships(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
|
||||
static const JsonDispatch dispatch_table[] = {
|
||||
{ "userName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, user_name), 0 },
|
||||
{ "groupName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, group_name), 0 },
|
||||
{ "service", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, service), 0 },
|
||||
{}
|
||||
};
|
||||
|
||||
LookupParameters p = {};
|
||||
int r;
|
||||
|
||||
assert(parameters);
|
||||
|
||||
r = json_dispatch(parameters, dispatch_table, NULL, 0, &p);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (streq_ptr(p.service, "io.systemd.NameServiceSwitch")) {
|
||||
|
||||
if (p.group_name) {
|
||||
_cleanup_(group_record_unrefp) GroupRecord *g = NULL;
|
||||
const char *last = NULL;
|
||||
char **i;
|
||||
|
||||
r = nss_group_record_by_name(p.group_name, &g);
|
||||
if (r == -ESRCH)
|
||||
return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
STRV_FOREACH(i, g->members) {
|
||||
|
||||
if (p.user_name && !streq_ptr(p.user_name, *i))
|
||||
continue;
|
||||
|
||||
if (last) {
|
||||
r = varlink_notifyb(link, JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(last)),
|
||||
JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(g->group_name))));
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
last = *i;
|
||||
}
|
||||
|
||||
if (!last)
|
||||
return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
|
||||
|
||||
return varlink_replyb(link, JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(last)),
|
||||
JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(g->group_name))));
|
||||
} else {
|
||||
_cleanup_free_ char *last_user_name = NULL, *last_group_name = NULL;
|
||||
|
||||
setgrent();
|
||||
|
||||
for (;;) {
|
||||
struct group *grp;
|
||||
const char* two[2], **users, **i;
|
||||
|
||||
errno = 0;
|
||||
grp = getgrent();
|
||||
if (!grp) {
|
||||
if (errno != 0)
|
||||
log_debug_errno(errno, "Failure while iterating through NSS group database, ignoring: %m");
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (p.user_name) {
|
||||
if (!strv_contains(grp->gr_mem, p.user_name))
|
||||
continue;
|
||||
|
||||
two[0] = p.user_name;
|
||||
two[1] = NULL;
|
||||
|
||||
users = two;
|
||||
} else
|
||||
users = (const char**) grp->gr_mem;
|
||||
|
||||
STRV_FOREACH(i, users) {
|
||||
|
||||
if (last_user_name) {
|
||||
assert(last_group_name);
|
||||
|
||||
r = varlink_notifyb(link, JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(last_user_name)),
|
||||
JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(last_group_name))));
|
||||
if (r < 0) {
|
||||
endgrent();
|
||||
return r;
|
||||
}
|
||||
|
||||
free(last_user_name);
|
||||
free(last_group_name);
|
||||
}
|
||||
|
||||
last_user_name = strdup(*i);
|
||||
last_group_name = strdup(grp->gr_name);
|
||||
if (!last_user_name || !last_group_name) {
|
||||
endgrent();
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
endgrent();
|
||||
|
||||
if (!last_user_name) {
|
||||
assert(!last_group_name);
|
||||
return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
|
||||
}
|
||||
|
||||
assert(last_group_name);
|
||||
|
||||
return varlink_replyb(link, JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(last_user_name)),
|
||||
JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(last_group_name))));
|
||||
}
|
||||
|
||||
} else if (streq_ptr(p.service, "io.systemd.Multiplexer")) {
|
||||
|
||||
_cleanup_free_ char *last_user_name = NULL, *last_group_name = NULL;
|
||||
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
|
||||
|
||||
if (p.group_name)
|
||||
r = membershipdb_by_group(p.group_name, USERDB_AVOID_MULTIPLEXER, &iterator);
|
||||
else if (p.user_name)
|
||||
r = membershipdb_by_user(p.user_name, USERDB_AVOID_MULTIPLEXER, &iterator);
|
||||
else
|
||||
r = membershipdb_all(USERDB_AVOID_MULTIPLEXER, &iterator);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
for (;;) {
|
||||
_cleanup_free_ char *user_name = NULL, *group_name = NULL;
|
||||
|
||||
r = membershipdb_iterator_get(iterator, &user_name, &group_name);
|
||||
if (r == -ESRCH)
|
||||
break;
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* If both group + user are specified do a-posteriori filtering */
|
||||
if (p.group_name && p.user_name && !streq(group_name, p.group_name))
|
||||
continue;
|
||||
|
||||
if (last_user_name) {
|
||||
assert(last_group_name);
|
||||
|
||||
r = varlink_notifyb(link, JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(last_user_name)),
|
||||
JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(last_group_name))));
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
free(last_user_name);
|
||||
free(last_group_name);
|
||||
}
|
||||
|
||||
last_user_name = TAKE_PTR(user_name);
|
||||
last_group_name = TAKE_PTR(group_name);
|
||||
}
|
||||
|
||||
if (!last_user_name) {
|
||||
assert(!last_group_name);
|
||||
return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
|
||||
}
|
||||
|
||||
assert(last_group_name);
|
||||
|
||||
return varlink_replyb(link, JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(last_user_name)),
|
||||
JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(last_group_name))));
|
||||
}
|
||||
|
||||
return varlink_error(link, "io.systemd.UserDatabase.BadService", NULL);
|
||||
}
|
||||
|
||||
static int process_connection(VarlinkServer *server, int fd) {
|
||||
_cleanup_(varlink_close_unrefp) Varlink *vl = NULL;
|
||||
int r;
|
||||
|
||||
r = varlink_server_add_connection(server, fd, &vl);
|
||||
if (r < 0) {
|
||||
fd = safe_close(fd);
|
||||
return log_error_errno(r, "Failed to add connection: %m");
|
||||
}
|
||||
|
||||
vl = varlink_ref(vl);
|
||||
|
||||
for (;;) {
|
||||
r = varlink_process(vl);
|
||||
if (r == -ENOTCONN) {
|
||||
log_debug("Connection terminated.");
|
||||
break;
|
||||
}
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to process connection: %m");
|
||||
if (r > 0)
|
||||
continue;
|
||||
|
||||
r = varlink_wait(vl, CONNECTION_IDLE_USEC);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to wait for connection events: %m");
|
||||
if (r == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int run(int argc, char *argv[]) {
|
||||
usec_t start_time, listen_idle_usec, last_busy_usec = USEC_INFINITY;
|
||||
_cleanup_(varlink_server_unrefp) VarlinkServer *server = NULL;
|
||||
_cleanup_close_ int lock = -1;
|
||||
unsigned n_iterations = 0;
|
||||
int m, listen_fd, r;
|
||||
|
||||
log_setup_service();
|
||||
|
||||
m = sd_listen_fds(false);
|
||||
if (m < 0)
|
||||
return log_error_errno(m, "Failed to determine number of listening fds: %m");
|
||||
if (m == 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No socket to listen on received.");
|
||||
if (m > 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Worker can only listen on a single socket at a time.");
|
||||
|
||||
listen_fd = SD_LISTEN_FDS_START;
|
||||
|
||||
r = fd_nonblock(listen_fd, false);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to turn off non-blocking mode for listening socket: %m");
|
||||
|
||||
r = varlink_server_new(&server, 0);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to allocate server: %m");
|
||||
|
||||
r = varlink_server_bind_method_many(
|
||||
server,
|
||||
"io.systemd.UserDatabase.GetUserRecord", vl_method_get_user_record,
|
||||
"io.systemd.UserDatabase.GetGroupRecord", vl_method_get_group_record,
|
||||
"io.systemd.UserDatabase.GetMemberships", vl_method_get_memberships);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to bind methods: %m");
|
||||
|
||||
r = getenv_bool("USERDB_FIXED_WORKER");
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse USERDB_FIXED_WORKER: %m");
|
||||
listen_idle_usec = r ? USEC_INFINITY : LISTEN_IDLE_USEC;
|
||||
|
||||
lock = userdb_nss_compat_disable();
|
||||
if (lock < 0)
|
||||
return log_error_errno(r, "Failed to disable userdb NSS compatibility: %m");
|
||||
|
||||
start_time = now(CLOCK_MONOTONIC);
|
||||
|
||||
for (;;) {
|
||||
_cleanup_close_ int fd = -1;
|
||||
usec_t n;
|
||||
|
||||
/* Exit the worker in regular intervals, to flush out all memory use */
|
||||
if (n_iterations++ > ITERATIONS_MAX) {
|
||||
log_debug("Exiting worker, processed %u iterations, that's enough.", n_iterations);
|
||||
break;
|
||||
}
|
||||
|
||||
n = now(CLOCK_MONOTONIC);
|
||||
if (n >= usec_add(start_time, RUNTIME_MAX_USEC)) {
|
||||
char buf[FORMAT_TIMESPAN_MAX];
|
||||
log_debug("Exiting worker, ran for %s, that's enough.", format_timespan(buf, sizeof(buf), usec_sub_unsigned(n, start_time), 0));
|
||||
break;
|
||||
}
|
||||
|
||||
if (last_busy_usec == USEC_INFINITY)
|
||||
last_busy_usec = n;
|
||||
else if (listen_idle_usec != USEC_INFINITY && n >= usec_add(last_busy_usec, listen_idle_usec)) {
|
||||
char buf[FORMAT_TIMESPAN_MAX];
|
||||
log_debug("Exiting worker, been idle for %s, .", format_timespan(buf, sizeof(buf), usec_sub_unsigned(n, last_busy_usec), 0));
|
||||
break;
|
||||
}
|
||||
|
||||
(void) rename_process("systemd-userwork: waiting...");
|
||||
|
||||
fd = accept4(listen_fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC);
|
||||
if (fd < 0)
|
||||
fd = -errno;
|
||||
|
||||
(void) rename_process("systemd-userwork: processing...");
|
||||
|
||||
if (fd == -EAGAIN)
|
||||
continue; /* The listening socket as SO_RECVTIMEO set, hence a time-out is expected
|
||||
* after a while, let's check if it's time to exit though. */
|
||||
if (fd == -EINTR)
|
||||
continue; /* Might be that somebody attached via strace, let's just continue in that
|
||||
* case */
|
||||
if (fd < 0)
|
||||
return log_error_errno(fd, "Failed to accept() from listening socket: %m");
|
||||
|
||||
if (now(CLOCK_MONOTONIC) <= usec_add(n, PRESSURE_SLEEP_TIME_USEC)) {
|
||||
struct pollfd pfd = {
|
||||
.fd = listen_fd,
|
||||
.events = POLLIN,
|
||||
};
|
||||
|
||||
/* We only slept a very short time? If so, let's see if there are more sockets
|
||||
* pending, and if so, let's ask our parent for more workers */
|
||||
|
||||
if (poll(&pfd, 1, 0) < 0)
|
||||
return log_error_errno(errno, "Failed to test for POLLIN on listening socket: %m");
|
||||
|
||||
if (FLAGS_SET(pfd.revents, POLLIN)) {
|
||||
pid_t parent;
|
||||
|
||||
parent = getppid();
|
||||
if (parent <= 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Parent already died?");
|
||||
|
||||
if (kill(parent, SIGUSR1) < 0)
|
||||
return log_error_errno(errno, "Failed to kill our own parent.");
|
||||
}
|
||||
}
|
||||
|
||||
(void) process_connection(server, TAKE_FD(fd));
|
||||
last_busy_usec = USEC_INFINITY;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_MAIN_FUNCTION(run);
|
@ -96,6 +96,8 @@ units = [
|
||||
'sockets.target.wants/'],
|
||||
['systemd-journald.socket', '',
|
||||
'sockets.target.wants/'],
|
||||
['systemd-userdbd.socket', 'ENABLE_USERDB',
|
||||
'sockets.target.wants/'],
|
||||
['systemd-networkd.socket', 'ENABLE_NETWORKD'],
|
||||
['systemd-poweroff.service', ''],
|
||||
['systemd-reboot.service', ''],
|
||||
@ -182,6 +184,7 @@ in_units = [
|
||||
['systemd-nspawn@.service', ''],
|
||||
['systemd-portabled.service', 'ENABLE_PORTABLED',
|
||||
'dbus-org.freedesktop.portable1.service'],
|
||||
['systemd-userdbd.service', 'ENABLE_USERDB'],
|
||||
['systemd-quotacheck.service', 'ENABLE_QUOTACHECK'],
|
||||
['systemd-random-seed.service', 'ENABLE_RANDOMSEED',
|
||||
'sysinit.target.wants/'],
|
||||
|
41
units/systemd-userdbd.service.in
Normal file
41
units/systemd-userdbd.service.in
Normal file
@ -0,0 +1,41 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1+
|
||||
#
|
||||
# This file is part of systemd.
|
||||
#
|
||||
# systemd is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
[Unit]
|
||||
Description=User Database Manager
|
||||
Documentation=man:systemd-userdbd.service(8)
|
||||
Requires=systemd-userdbd.socket
|
||||
After=systemd-userdbd.socket
|
||||
Before=sysinit.target
|
||||
DefaultDependencies=no
|
||||
|
||||
[Service]
|
||||
CapabilityBoundingSet=CAP_DAC_READ_SEARCH
|
||||
ExecStart=@rootlibexecdir@/systemd-userdbd
|
||||
IPAddressDeny=any
|
||||
LimitNOFILE=@HIGH_RLIMIT_NOFILE@
|
||||
LockPersonality=yes
|
||||
MemoryDenyWriteExecute=yes
|
||||
NoNewPrivileges=yes
|
||||
PrivateDevices=yes
|
||||
ProtectControlGroups=yes
|
||||
ProtectHome=yes
|
||||
ProtectHostname=yes
|
||||
ProtectKernelLogs=yes
|
||||
ProtectKernelModules=yes
|
||||
ProtectSystem=strict
|
||||
RestrictAddressFamilies=AF_UNIX AF_NETLINK AF_INET AF_INET6
|
||||
RestrictNamespaces=yes
|
||||
RestrictRealtime=yes
|
||||
RestrictSUIDSGID=yes
|
||||
SystemCallArchitectures=native
|
||||
SystemCallErrorNumber=EPERM
|
||||
SystemCallFilter=@system-service
|
||||
Type=notify
|
||||
@SERVICE_WATCHDOG@
|
19
units/systemd-userdbd.socket
Normal file
19
units/systemd-userdbd.socket
Normal file
@ -0,0 +1,19 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1+
|
||||
#
|
||||
# This file is part of systemd.
|
||||
#
|
||||
# systemd is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
[Unit]
|
||||
Description=User Database Manager Socket
|
||||
Documentation=man:systemd-userdbd.service(8)
|
||||
DefaultDependencies=no
|
||||
Before=sockets.target
|
||||
|
||||
[Socket]
|
||||
ListenStream=/run/systemd/userdb/io.systemd.Multiplexer
|
||||
Symlinks=/run/systemd/userdb/io.systemd.NameServiceSwitch
|
||||
SocketMode=0666
|
Loading…
x
Reference in New Issue
Block a user