mirror of
https://github.com/troglobit/finit.git
synced 2026-10-12 10:12:32 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
530d6703f1 | ||
|
|
cec76f18a3 | ||
|
|
ee6d4a1dae | ||
|
|
0548485be1 | ||
|
|
1b10c3746e | ||
|
|
24911ebbc2 | ||
|
|
88a9e6575b | ||
|
|
9bfbdc6bac | ||
|
|
c016965371 | ||
|
|
263ea929fd | ||
|
|
cc5e0c5175 | ||
|
|
417abf9000 | ||
|
|
61b0e0f3e6 | ||
|
|
bad7c5c99a | ||
|
|
4c16190831 |
Executable
+29
@@ -0,0 +1,29 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# Extract the topmost release entry from doc/ChangeLog.md and unwrap
|
||||||
|
# its paragraphs, so the GitHub releases page can wrap them to the
|
||||||
|
# browser width instead of showing the hard 72-column breaks.
|
||||||
|
#
|
||||||
|
# Usage: .github/scripts/extract-changelog.sh < doc/ChangeLog.md > release.md
|
||||||
|
#
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# The entry sits between the first two underlines, minus the blank
|
||||||
|
# line separating it from the next heading.
|
||||||
|
awk '/^-----*$/{if (x == 1) exit; x=1;next}x' | head -n -1 |
|
||||||
|
|
||||||
|
# Blank lines, headings, blockquotes, tables, and list items (at any
|
||||||
|
# indent) start a new block, everything else continues the current one.
|
||||||
|
awk '
|
||||||
|
function flush() { if (buf) print buf; buf = "" }
|
||||||
|
{
|
||||||
|
if (/^$/) { flush(); print; blank = 1; next }
|
||||||
|
if (/^ *([-*] |[0-9]+\. |[>#|])/ || blank || !buf || brk)
|
||||||
|
{ flush(); buf = $0 }
|
||||||
|
else
|
||||||
|
{ sub(/^ +/, ""); buf = buf " " $0 }
|
||||||
|
blank = 0
|
||||||
|
brk = / $/
|
||||||
|
}
|
||||||
|
END { flush() }
|
||||||
|
'
|
||||||
@@ -99,7 +99,7 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get -y update
|
sudo apt-get -y update
|
||||||
sudo apt-get -y install pkg-config tree jq libcap-dev libconfuse-dev libblkid-dev
|
sudo apt-get -y install pkg-config tree jq libcap-dev libconfuse-dev libblkid-dev libpam0g-dev
|
||||||
wget https://github.com/troglobit/libuev/releases/download/v2.4.1/libuev-2.4.1.tar.xz
|
wget https://github.com/troglobit/libuev/releases/download/v2.4.1/libuev-2.4.1.tar.xz
|
||||||
wget https://github.com/troglobit/libite/releases/download/v2.6.2/libite-2.6.2.tar.gz
|
wget https://github.com/troglobit/libite/releases/download/v2.6.2/libite-2.6.2.tar.gz
|
||||||
tar xf libuev-2.4.1.tar.xz
|
tar xf libuev-2.4.1.tar.xz
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ jobs:
|
|||||||
make -j1 release || (cat $dir/test/test-suite.log; false)
|
make -j1 release || (cat $dir/test/test-suite.log; false)
|
||||||
ls -lF ../
|
ls -lF ../
|
||||||
mkdir -p artifacts/
|
mkdir -p artifacts/
|
||||||
mv ../*.tar.* artifacts/
|
mv ../*.tar.gz artifacts/
|
||||||
- name: Upload Test Results
|
- name: Upload Test Results
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v7
|
||||||
@@ -69,8 +69,7 @@ jobs:
|
|||||||
path: finit-*/_build/sub/test/*.log
|
path: finit-*/_build/sub/test/*.log
|
||||||
- name: Extract ChangeLog entry ...
|
- name: Extract ChangeLog entry ...
|
||||||
run: |
|
run: |
|
||||||
awk '/-----*/{if (x == 1) exit; x=1;next}x' doc/ChangeLog.md \
|
.github/scripts/extract-changelog.sh < doc/ChangeLog.md > release.md
|
||||||
|head -n -1 > release.md
|
|
||||||
cat release.md
|
cat release.md
|
||||||
- uses: ncipollo/release-action@v1
|
- uses: ncipollo/release-action@v1
|
||||||
with:
|
with:
|
||||||
|
|||||||
+1
-11
@@ -49,19 +49,9 @@ distclean-local:
|
|||||||
|
|
||||||
# Target to run when building a release
|
# Target to run when building a release
|
||||||
release: distcheck
|
release: distcheck
|
||||||
@for file in $(DIST_ARCHIVES); do \
|
|
||||||
md5sum $$file > ../$$file.md5; \
|
|
||||||
sha256sum $$file > ../$$file.sha256; \
|
|
||||||
done
|
|
||||||
@mv $(DIST_ARCHIVES) ../
|
@mv $(DIST_ARCHIVES) ../
|
||||||
@echo
|
@echo
|
||||||
@echo "Resulting release files:"
|
@echo "Resulting release tarball(s): $(DIST_ARCHIVES)"
|
||||||
@echo "================================================================="
|
|
||||||
@for file in $(DIST_ARCHIVES); do \
|
|
||||||
printf "$$file \tDistribution tarball\n"; \
|
|
||||||
printf "$$file.md5\t"; cat ../$$file.md5 | cut -f1 -d' '; \
|
|
||||||
printf "$$file.sha256\t"; cat ../$$file.sha256 | cut -f1 -d' '; \
|
|
||||||
done
|
|
||||||
|
|
||||||
DISTCHECK_CONFIGURE_FLAGS = --prefix=/usr --sysconfdir=/etc --localstatedir=/var \
|
DISTCHECK_CONFIGURE_FLAGS = --prefix=/usr --sysconfdir=/etc --localstatedir=/var \
|
||||||
--enable-testserv-plugin --enable-x11-common-plugin \
|
--enable-testserv-plugin --enable-x11-common-plugin \
|
||||||
|
|||||||
+29
-1
@@ -40,7 +40,7 @@ AC_PROG_MKDIR_P
|
|||||||
|
|
||||||
# Configuration.
|
# Configuration.
|
||||||
AC_CHECK_HEADERS([termios.h sys/ioctl.h mntent.h sys/sysmacros.h])
|
AC_CHECK_HEADERS([termios.h sys/ioctl.h mntent.h sys/sysmacros.h])
|
||||||
AC_CHECK_FUNCS([strstr getopt getmntent getmntent_r])
|
AC_CHECK_FUNCS([strstr getopt getmntent getmntent_r close_range])
|
||||||
|
|
||||||
# Check for uint[8,16,32]_t
|
# Check for uint[8,16,32]_t
|
||||||
AC_TYPE_UINT8_T
|
AC_TYPE_UINT8_T
|
||||||
@@ -85,6 +85,11 @@ AC_ARG_ENABLE(libcap,
|
|||||||
AS_HELP_STRING([--disable-libcap], [Disable Linux capabilities support]),,[
|
AS_HELP_STRING([--disable-libcap], [Disable Linux capabilities support]),,[
|
||||||
enable_libcap=yes])
|
enable_libcap=yes])
|
||||||
|
|
||||||
|
AC_ARG_ENABLE(pam,
|
||||||
|
AS_HELP_STRING([--disable-pam], [Disable PAM session support for services]),
|
||||||
|
[pam_explicit=yes],
|
||||||
|
[enable_pam=yes])
|
||||||
|
|
||||||
AC_ARG_ENABLE(redirect,
|
AC_ARG_ENABLE(redirect,
|
||||||
AS_HELP_STRING([--disable-redirect], [Disable redirection of service output to /dev/null]),,[
|
AS_HELP_STRING([--disable-redirect], [Disable redirection of service output to /dev/null]),,[
|
||||||
enable_redirect=yes])
|
enable_redirect=yes])
|
||||||
@@ -235,6 +240,26 @@ AS_IF([test "x$enable_libcap" = "xyes"], [
|
|||||||
])
|
])
|
||||||
])
|
])
|
||||||
|
|
||||||
|
AS_IF([test "x$enable_pam" = "xyes"], [
|
||||||
|
AS_IF([test "x$enable_static" = "xyes"], [
|
||||||
|
AS_IF([test "x$pam_explicit" = "xyes"], [
|
||||||
|
AC_MSG_ERROR([--enable-pam does not work with --enable-static, PAM dlopen()s its modules])], [
|
||||||
|
AC_MSG_NOTICE([PAM disabled for static build, PAM dlopen()s its modules])
|
||||||
|
enable_pam=no])])
|
||||||
|
])
|
||||||
|
AS_IF([test "x$enable_pam" = "xyes"], [
|
||||||
|
AC_CHECK_HEADER([security/pam_appl.h], [], [
|
||||||
|
AC_MSG_WARN([PAM headers not found, PAM support disabled])
|
||||||
|
enable_pam=no])
|
||||||
|
AS_IF([test "x$enable_pam" = "xyes"], [
|
||||||
|
AC_CHECK_LIB([pam], [pam_start], [
|
||||||
|
AC_DEFINE(HAVE_LIBPAM, 1, [Have Linux-PAM for per-service session support])
|
||||||
|
LIBS="$LIBS -lpam"
|
||||||
|
], [
|
||||||
|
AC_MSG_WARN([libpam not found, PAM support disabled])
|
||||||
|
enable_pam=no])])
|
||||||
|
])
|
||||||
|
|
||||||
AS_IF([test "x$enable_fastboot" = "xyes"], [
|
AS_IF([test "x$enable_fastboot" = "xyes"], [
|
||||||
AC_DEFINE(FAST_BOOT, 1, [Skip fsck check on filesystems listed in /etc/fstab])])
|
AC_DEFINE(FAST_BOOT, 1, [Skip fsck check on filesystems listed in /etc/fstab])])
|
||||||
|
|
||||||
@@ -257,6 +282,8 @@ AS_IF([test "x$enable_dbus" = "xyes"], [
|
|||||||
AC_DEFINE(HAVE_DBUS, 1, [Build D-Bus support via libink])])
|
AC_DEFINE(HAVE_DBUS, 1, [Build D-Bus support via libink])])
|
||||||
AM_CONDITIONAL(DBUS, [test "x$enable_dbus" = "xyes"])
|
AM_CONDITIONAL(DBUS, [test "x$enable_dbus" = "xyes"])
|
||||||
|
|
||||||
|
AM_CONDITIONAL(PAM, [test "x$enable_pam" = "xyes"])
|
||||||
|
|
||||||
### With features ##############################################################################
|
### With features ##############################################################################
|
||||||
AS_IF([test "x$bash_dir" = "xyes"], [
|
AS_IF([test "x$bash_dir" = "xyes"], [
|
||||||
PKG_CHECK_MODULES([BASH_COMPLETION], [bash-completion >= 2.0],
|
PKG_CHECK_MODULES([BASH_COMPLETION], [bash-completion >= 2.0],
|
||||||
@@ -461,6 +488,7 @@ Optional features:
|
|||||||
Replacement libsystemd: $with_libsystemd
|
Replacement libsystemd: $with_libsystemd
|
||||||
Use cgroup v2.........: $enable_cgroup
|
Use cgroup v2.........: $enable_cgroup
|
||||||
Use libcap............: $enable_libcap
|
Use libcap............: $enable_libcap
|
||||||
|
Use PAM...............: $enable_pam
|
||||||
Parse kernel cmdline..: $enable_kernel_cmdline
|
Parse kernel cmdline..: $enable_kernel_cmdline
|
||||||
Keep kernel logging...: $enable_kernel_logging
|
Keep kernel logging...: $enable_kernel_logging
|
||||||
Skip fsck check.......: $enable_fastboot
|
Skip fsck check.......: $enable_fastboot
|
||||||
|
|||||||
@@ -85,6 +85,10 @@ All relevant changes are documented in this file.
|
|||||||
service, `command = { "/lib/systemd/systemd-udevd", "-udevd" }`, and Finit
|
service, `command = { "/lib/systemd/systemd-udevd", "-udevd" }`, and Finit
|
||||||
starts the first one it finds. The line-based format could only express
|
starts the first one it finds. The line-based format could only express
|
||||||
this by repeating the whole stanza per candidate
|
this by repeating the whole stanza per candidate
|
||||||
|
- New `pam = "NAME"` setting for run/task/service/sysv blocks, runs the
|
||||||
|
service inside a PAM session set up from `/etc/pam.d/NAME`, so the `session`
|
||||||
|
stack applies to the process that becomes the daemon, e.g. limits from
|
||||||
|
`pam_limits`, which override a per-service `rlimit`. Issue #420
|
||||||
- Finit now ships with a built-in brokerless D-Bus implementation, **libink**,
|
- Finit now ships with a built-in brokerless D-Bus implementation, **libink**,
|
||||||
exposing the running init system as a peer on its own private bus at
|
exposing the running init system as a peer on its own private bus at
|
||||||
`/run/finit/bus`, and -- opportunistically -- registering `org.finit` on the
|
`/run/finit/bus`, and -- opportunistically -- registering `org.finit` on the
|
||||||
@@ -136,6 +140,16 @@ All relevant changes are documented in this file.
|
|||||||
e.g. `killed by SIGKILL` or `killed by SIGSEGV, core dumped`, in place of
|
e.g. `killed by SIGKILL` or `killed by SIGSEGV, core dumped`, in place of
|
||||||
the bare numeric `by signal: N`. Gives operators a much stronger breadcrumb
|
the bare numeric `by signal: N`. Gives operators a much stronger breadcrumb
|
||||||
when a daemon dies unexpectedly
|
when a daemon dies unexpectedly
|
||||||
|
- Service `user:` and `group:` names, including supplementary groups, can
|
||||||
|
now be up to 63 characters, up from 15. Longer names were silently
|
||||||
|
truncated before the lookup, so the service never started. By Anees Rehman
|
||||||
|
- `stop:` and `reload:` scripts take a timeout of their own, `stop:5,/path`,
|
||||||
|
defaulting to the kill delay as before. Such a line used to crash Finit at
|
||||||
|
config load
|
||||||
|
- Shutdown now stops after the first successful remount of `/` read only.
|
||||||
|
The plain `mount -n -o remount,ro /`, which reads `/etc/fstab`, is only
|
||||||
|
used when both device-agnostic forms fail, the same order as Debian's
|
||||||
|
`umountroot`. By Anees Rehman
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
@@ -157,6 +171,17 @@ All relevant changes are documented in this file.
|
|||||||
- Fix misspelled `SIGUNKOWN` returned by `sig_name()` for unknown signal
|
- Fix misspelled `SIGUNKOWN` returned by `sig_name()` for unknown signal
|
||||||
numbers, now spelled correctly as `SIGUNKNOWN`. Surfaced by the new restart
|
numbers, now spelled correctly as `SIGUNKNOWN`. Surfaced by the new restart
|
||||||
log above
|
log above
|
||||||
|
- Fix mode of existing `d`/`D` directories in tmpfiles, the chmod branch
|
||||||
|
never ran because the directory creation swallowed `EEXIST`. Mode and owner
|
||||||
|
of `d`/`D`, `e`, and `f`/`F` entries, and of the service directories
|
||||||
|
`mksubsysd()` creates, are now set through an open fd and only when they
|
||||||
|
differ, so an immutable directory like `/var/empty` no longer warns on every
|
||||||
|
boot. By Sam Brkopac
|
||||||
|
- Fix a lost reload for a service paused mid-reload by a condition in
|
||||||
|
flux. A second reload arriving meanwhile re-parsed the unchanged .conf
|
||||||
|
and cleared the pending mark, so the service resumed without ever being
|
||||||
|
reloaded. Seen on Infix, where sshd kept its old listen addresses after
|
||||||
|
a change that landed as two reloads in a row
|
||||||
|
|
||||||
[4.17][] - 2026-04-28
|
[4.17][] - 2026-04-28
|
||||||
---------------------
|
---------------------
|
||||||
|
|||||||
@@ -15,6 +15,11 @@ and `/dev/disk/by-label/` symlinks after. keventd is enabled by
|
|||||||
default, so this is a hard requirement unless you build with
|
default, so this is a hard requirement unless you build with
|
||||||
`--without-keventd`.
|
`--without-keventd`.
|
||||||
|
|
||||||
|
PAM session support for services is built by default. It needs
|
||||||
|
[Linux-PAM][] (-lpam); the build falls back to no PAM support when that
|
||||||
|
library is missing. A static build, `--enable-static`, disables PAM
|
||||||
|
too, since libpam loads its modules with `dlopen()`.
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
> Most free/open source software packages that use `configure` default
|
> Most free/open source software packages that use `configure` default
|
||||||
> to install to `/usr/local`. However, some Linux distributions do no
|
> to install to `/usr/local`. However, some Linux distributions do no
|
||||||
@@ -54,6 +59,14 @@ Below are a few of the main switches to configure:
|
|||||||
built-ins (.o files) and all external libraries, except the C library
|
built-ins (.o files) and all external libraries, except the C library
|
||||||
will be linked statically.
|
will be linked statically.
|
||||||
|
|
||||||
|
* `--disable-pam`: Opt out of Finit's built-in PAM session support,
|
||||||
|
enabled by default, which lets a service declare `pam = "name"` and
|
||||||
|
run inside a session set up from `/etc/pam.d/name`. Needs libpam,
|
||||||
|
falls back to disabled when it is missing, and is also disabled for
|
||||||
|
`--enable-static` builds. Asking for `--enable-pam` and
|
||||||
|
`--enable-static` together is an error rather than a fallback. See
|
||||||
|
[PAM Sessions](config/pam.md)
|
||||||
|
|
||||||
* `--enable-kernel-cmdline`: Enable Finit pre-4.1 parsing of init args from
|
* `--enable-kernel-cmdline`: Enable Finit pre-4.1 parsing of init args from
|
||||||
`/proc/cmdline`, this is *not recommended* since Finit may be running as the
|
`/proc/cmdline`, this is *not recommended* since Finit may be running as the
|
||||||
init for container apps that can see the host's `/proc` filesystem
|
init for container apps that can see the host's `/proc` filesystem
|
||||||
@@ -285,3 +298,4 @@ it only for debugging start up issues when Finit crashes.
|
|||||||
[libite]: https://github.com/troglobit/libite
|
[libite]: https://github.com/troglobit/libite
|
||||||
[libConfuse]: https://github.com/libconfuse/libconfuse
|
[libConfuse]: https://github.com/libconfuse/libconfuse
|
||||||
[util-linux]: https://github.com/util-linux/util-linux
|
[util-linux]: https://github.com/util-linux/util-linux
|
||||||
|
[Linux-PAM]: https://github.com/linux-pam/linux-pam
|
||||||
|
|||||||
@@ -189,6 +189,9 @@ ps -o user,pid,cmd -p $(pidof nginx)
|
|||||||
- Services without `capabilities` use standard privilege dropping:
|
- Services without `capabilities` use standard privilege dropping:
|
||||||
- Services with a non-root `user` have no special capabilities
|
- Services with a non-root `user` have no special capabilities
|
||||||
- Services without `user` run as root with full capabilities
|
- Services without `user` run as root with full capabilities
|
||||||
|
- A capability granted by `pam_cap.so` in a [PAM session](pam.md) is
|
||||||
|
merged into this set, but only when `capabilities` is also set.
|
||||||
|
Without it the grant is lost when privileges drop
|
||||||
- Some very old binaries may not work correctly with ambient capabilities
|
- Some very old binaries may not work correctly with ambient capabilities
|
||||||
- File system capabilities are not managed by Finit (use `setcap` for that)
|
- File system capabilities are not managed by Finit (use `setcap` for that)
|
||||||
|
|
||||||
|
|||||||
@@ -261,7 +261,7 @@ Worth knowing
|
|||||||
$SYSLOGD_ARGS"` working with an `envfile`.
|
$SYSLOGD_ARGS"` working with an `envfile`.
|
||||||
* New settings only appear in the block format. The first are the
|
* New settings only appear in the block format. The first are the
|
||||||
[per-service directories](service-opts.md#service-directories),
|
[per-service directories](service-opts.md#service-directories),
|
||||||
`runtime-dir` and friends.
|
`runtime-dir` and friends, and [`pam`](pam.md).
|
||||||
|
|
||||||
For the full description of every key, see the rest of the
|
For the full description of every key, see the rest of the
|
||||||
[Configuration](index.md) section.
|
[Configuration](index.md) section.
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
PAM Sessions
|
||||||
|
============
|
||||||
|
|
||||||
|
`pam = "NAME"` runs a service inside a PAM session set up from
|
||||||
|
`/etc/pam.d/NAME`. The `session` stack in that file runs for the
|
||||||
|
process that goes on to become the daemon, so modules like `pam_limits`,
|
||||||
|
`pam_env`, or `pam_keyinit` see the service the way they see a login.
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
A display server that needs the session a login would have arranged for
|
||||||
|
it:
|
||||||
|
|
||||||
|
```conf
|
||||||
|
service weston {
|
||||||
|
user = "weston"
|
||||||
|
pam = "weston-autologin"
|
||||||
|
command = "/usr/bin/weston --continue-without-input"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
With `/etc/pam.d/weston-autologin`:
|
||||||
|
|
||||||
|
```
|
||||||
|
auth required pam_permit.so
|
||||||
|
account required pam_unix.so
|
||||||
|
session required pam_unix.so
|
||||||
|
session required pam_limits.so
|
||||||
|
```
|
||||||
|
|
||||||
|
The `auth` line is needed even though nothing is ever authenticated.
|
||||||
|
Opening the session goes through `pam_setcred()`, which consults the
|
||||||
|
`auth` stack, and an empty stack comes back as a permission denial, so a
|
||||||
|
pam.d file with only `account` and `session` lines keeps the service
|
||||||
|
from starting.
|
||||||
|
|
||||||
|
The value names a file in `/etc/pam.d`, it is not a path. A value
|
||||||
|
holding `/` or `..`, or one too long to fit, is refused, with an error
|
||||||
|
in the log when the .conf file is read. The service does not start
|
||||||
|
either, `initctl status` reports it as missing. Like the per-service
|
||||||
|
directory keys, `pam` exists in the block format only.
|
||||||
|
|
||||||
|
## No Authentication
|
||||||
|
|
||||||
|
Finit runs the account and session stacks, `pam_acct_mgmt()`,
|
||||||
|
`pam_setcred()`, and `pam_open_session()`, and never
|
||||||
|
`pam_authenticate()`. A module that asks a question gets a
|
||||||
|
conversation error back and its entry in the stack fails.
|
||||||
|
|
||||||
|
A service whose account is denied, an expired account say, does not
|
||||||
|
start, and neither does one naming a pam.d file that is not installed.
|
||||||
|
The child exits 71 (`EX_OSERR`), `initctl status` shows the service as
|
||||||
|
crashed, and PAM's own reason is in the log.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
PAM support is built by default, see [Building Finit](../build.md).
|
||||||
|
Without it, e.g. after `--disable-pam`, a service declaring `pam` is
|
||||||
|
refused rather than started with the stacks skipped:
|
||||||
|
|
||||||
|
weston: pam weston-autologin requires Finit built with --enable-pam
|
||||||
|
|
||||||
|
## Which Blocks Take It
|
||||||
|
|
||||||
|
`service`, `task`, `run`, and `sysv`. Not `tty`: `login` opens a
|
||||||
|
session of its own there.
|
||||||
|
|
||||||
|
Every fork gets its own session, so the `pre:`, `post:`, `ready:`, and
|
||||||
|
`cleanup:` scripts each open and close one too, as do the stop and
|
||||||
|
reload scripts and the `stop` call on a `sysv` script.
|
||||||
|
|
||||||
|
A refused value stops those too. A `pre:` script forks before the
|
||||||
|
start-time check runs, so it exits 71 without running instead of the
|
||||||
|
service being reported missing.
|
||||||
|
|
||||||
|
## The User
|
||||||
|
|
||||||
|
`user` decides who the session is for. Without it the session is for
|
||||||
|
root.
|
||||||
|
|
||||||
|
systemd documents the opposite for its equivalent. `systemd.exec(5)`
|
||||||
|
says `PAMName=` is "only useful in conjunction with the `User=`
|
||||||
|
setting, and is otherwise ignored". That was true of systemd up to and
|
||||||
|
including v256, v257 changed it to open a session for the manager's own
|
||||||
|
user, and the man page was never updated. Finit does what v257 does.
|
||||||
|
|
||||||
|
A service with a [controlling tty](tty.md#controlling-tty-for-services)
|
||||||
|
has it passed to PAM as `PAM_TTY`, for the modules that care which
|
||||||
|
terminal a session is on.
|
||||||
|
|
||||||
|
## Precedence
|
||||||
|
|
||||||
|
Three places where PAM and a Finit setting cover the same ground:
|
||||||
|
|
||||||
|
- `pam_limits` overrides a per-service `rlimit`. A block asking for
|
||||||
|
`nofile = 4096` under a `limits.conf` that says 512 gets 512.
|
||||||
|
- `pam_env` overrides Finit's own environment defaults, `PATH`, `USER`,
|
||||||
|
`LOGNAME`, and `HOME`, and `envfile` in turn overrides `pam_env`. A
|
||||||
|
`HOME` from the session stack also moves the working directory, which
|
||||||
|
otherwise is the home directory from `/etc/passwd`.
|
||||||
|
- The groups from `/etc/group` and `extra-groups` override `pam_group`.
|
||||||
|
|
||||||
|
## The Session Keeper
|
||||||
|
|
||||||
|
A helper is forked next to the service to close the session when the
|
||||||
|
service exits, `(finit-pam)`:
|
||||||
|
|
||||||
|
```
|
||||||
|
CGroup : /system/weston cpu 0 [100, max] mem [--.--, max]
|
||||||
|
|- 312 /usr/bin/weston --continue-without-input
|
||||||
|
`- 313 (finit-pam)
|
||||||
|
```
|
||||||
|
|
||||||
|
There is one per fork. It runs as the service's user, in the service's
|
||||||
|
cgroup, and stopping the service takes it along.
|
||||||
|
|
||||||
|
A daemon that reaps children in its own `wait()` loop will see a child
|
||||||
|
it never forked. systemd has the same property, with `(sd-pam)`.
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
- `type = "forking"` closes the session early. The initial process
|
||||||
|
exits by design, the keeper's parent-death signal fires with it, and
|
||||||
|
the session is closed while the real daemon runs on. Finit warns
|
||||||
|
about the combination when it reads the .conf file, and starts the
|
||||||
|
service anyway:
|
||||||
|
|
||||||
|
/etc/finit.d/foo.conf: foo: pam with type = forking closes the
|
||||||
|
session when the initial process exits
|
||||||
|
|
||||||
|
- A daemon whose initial thread exits while the process lives closes the
|
||||||
|
session the same way. The parent-death signal follows the thread that
|
||||||
|
forked the keeper, not the process.
|
||||||
|
- A capability granted by `pam_cap.so` is only kept when the service
|
||||||
|
also sets [`capabilities`](capabilities.md).
|
||||||
|
- The keeper shares the service's cgroup, so an empty cgroup directory
|
||||||
|
can outlive a stop until the next start reuses it. Cosmetic.
|
||||||
|
- A module that blocks has no time bound. `pam_open_session()` waits
|
||||||
|
for as long as the module does, `pam_ldap` against an unreachable
|
||||||
|
server, say, or `pam_mount` on a hung network mount. The fork has already succeeded by then, so the service
|
||||||
|
reaches the running state and stays there with no daemon behind it.
|
||||||
|
Nothing crashes and nothing restarts.
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- [Service Options](service-opts.md) - the other run/task/service keys
|
||||||
|
- [Linux Capabilities](capabilities.md) - the key `pam_cap.so` needs
|
||||||
|
- [Building Finit](../build.md) - `--disable-pam` and its dependency
|
||||||
|
- [pam(8)](https://man7.org/linux/man-pages/man8/pam.8.html) - the PAM library
|
||||||
|
- [pam.d(5)](https://man7.org/linux/man-pages/man5/pam.d.5.html) - the file format
|
||||||
|
- [pam_limits(8)](https://man7.org/linux/man-pages/man8/pam_limits.8.html) - limits from `limits.conf`
|
||||||
@@ -160,6 +160,9 @@ albeit deprecated.
|
|||||||
each `/etc/finit.d/*.conf` read. I.e., a set of task/run/service
|
each `/etc/finit.d/*.conf` read. I.e., a set of task/run/service
|
||||||
blocks can share the same rlimits if they are in the same .conf.
|
blocks can share the same rlimits if they are in the same .conf.
|
||||||
|
|
||||||
|
For a service with [`pam`](pam.md), `pam_limits` runs after these
|
||||||
|
limits are applied, so `limits.conf` has the last word.
|
||||||
|
|
||||||
Miscellaneous Settings
|
Miscellaneous Settings
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ the `ps` command we can see that the process is started with:
|
|||||||
foo -n --extra-arg=bar -s -x
|
foo -n --extra-arg=bar -s -x
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
|
For a service with [`pam`](pam.md), the session environment is applied
|
||||||
|
before `envfile`, so the file overrides anything `pam_env` set.
|
||||||
|
|
||||||
> The leading `-` on `envfile` determines if Finit should treat a
|
> The leading `-` on `envfile` determines if Finit should treat a
|
||||||
> missing environment file as blocking the start of the service or not.
|
> missing environment file as blocking the start of the service or not.
|
||||||
> When `-` is used, a missing environment file does *not* block the
|
> When `-` is used, a missing environment file does *not* block the
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ Other run/task/service settings are:
|
|||||||
* `log {}` -- see [Redirecting Output](logging.md#redirecting-output)
|
* `log {}` -- see [Redirecting Output](logging.md#redirecting-output)
|
||||||
* `tty` -- see [Controlling TTY](tty.md#controlling-tty-for-services)
|
* `tty` -- see [Controlling TTY](tty.md#controlling-tty-for-services)
|
||||||
* `notify` -- see [Service Synchronization](service-sync.md)
|
* `notify` -- see [Service Synchronization](service-sync.md)
|
||||||
|
* `pam` -- see the [PAM Sessions](pam.md) section
|
||||||
* `if` -- see [Conditional Execution](services.md#conditional-execution)
|
* `if` -- see [Conditional Execution](services.md#conditional-execution)
|
||||||
* `type = "forking"` -- see description of the [service](services.md) block
|
* `type = "forking"` -- see description of the [service](services.md) block
|
||||||
* a leading `-` on `command` -- see
|
* a leading `-` on `command` -- see
|
||||||
|
|||||||
@@ -178,6 +178,26 @@ See the [Linux Capabilities](config/capabilities.md) section for detailed
|
|||||||
information, examples, and security best practices.
|
information, examples, and security best practices.
|
||||||
|
|
||||||
|
|
||||||
|
**PAM Sessions**
|
||||||
|
|
||||||
|
A service can run inside a PAM session, so the `session` stack in
|
||||||
|
`/etc/pam.d` applies to it, e.g. limits from `pam_limits`:
|
||||||
|
|
||||||
|
```conf
|
||||||
|
service weston {
|
||||||
|
user = "weston"
|
||||||
|
pam = "weston-autologin"
|
||||||
|
command = "/usr/bin/weston --continue-without-input"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Without a `user` the session is for root. Requires a build with PAM
|
||||||
|
support, which is the default; see `--disable-pam`.
|
||||||
|
|
||||||
|
See the [PAM Sessions](config/pam.md) section for the pam.d file the
|
||||||
|
example needs, and for how PAM and Finit settings interact.
|
||||||
|
|
||||||
|
|
||||||
**Supplementary Groups**
|
**Supplementary Groups**
|
||||||
|
|
||||||
Finit supports supplementary groups for services, allowing them to access
|
Finit supports supplementary groups for services, allowing them to access
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ nav:
|
|||||||
- Logging: config/logging.md
|
- Logging: config/logging.md
|
||||||
- Cgroups: config/cgroups.md
|
- Cgroups: config/cgroups.md
|
||||||
- Capabilities: config/capabilities.md
|
- Capabilities: config/capabilities.md
|
||||||
|
- PAM Sessions: config/pam.md
|
||||||
- Templating: config/templating.md
|
- Templating: config/templating.md
|
||||||
- SysV Compatibility: config/sysv.md
|
- SysV Compatibility: config/sysv.md
|
||||||
- Rescue Mode: config/rescue.md
|
- Rescue Mode: config/rescue.md
|
||||||
|
|||||||
@@ -76,6 +76,9 @@ endif
|
|||||||
if DBUS
|
if DBUS
|
||||||
finit_SOURCES += dbus.c
|
finit_SOURCES += dbus.c
|
||||||
endif
|
endif
|
||||||
|
if PAM
|
||||||
|
finit_SOURCES += pam.c pam.h
|
||||||
|
endif
|
||||||
|
|
||||||
pkginclude_HEADERS = cgroup.h cond.h conf.h finit.h helpers.h log.h \
|
pkginclude_HEADERS = cgroup.h cond.h conf.h finit.h helpers.h log.h \
|
||||||
plugin.h svc.h service.h
|
plugin.h svc.h service.h
|
||||||
|
|||||||
+37
@@ -276,6 +276,7 @@ static cfg_opt_t svc_opts[] = {
|
|||||||
|
|
||||||
CFG_STR_LIST("capabilities", NULL, CFGF_NODEFAULT),
|
CFG_STR_LIST("capabilities", NULL, CFGF_NODEFAULT),
|
||||||
CFG_STR_LIST("caps", NULL, CFGF_NODEFAULT), /* alias */
|
CFG_STR_LIST("caps", NULL, CFGF_NODEFAULT), /* alias */
|
||||||
|
CFG_STR ("pam", NULL, CFGF_NODEFAULT),
|
||||||
CFG_STR ("runtime-dir", NULL, CFGF_NODEFAULT),
|
CFG_STR ("runtime-dir", NULL, CFGF_NODEFAULT),
|
||||||
CFG_STR ("state-dir", NULL, CFGF_NODEFAULT),
|
CFG_STR ("state-dir", NULL, CFGF_NODEFAULT),
|
||||||
CFG_STR ("cache-dir", NULL, CFGF_NODEFAULT),
|
CFG_STR ("cache-dir", NULL, CFGF_NODEFAULT),
|
||||||
@@ -1485,6 +1486,41 @@ static void dirs_translate(cfg_t *sec, svc_t *svc, char *file)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Also block format only. The value names a file in /etc/pam.d, so a
|
||||||
|
* path is refused rather than followed.
|
||||||
|
*
|
||||||
|
* A bad value is stored anyway, or as much of it as fits, and
|
||||||
|
* service_start() refuses to start the service. Dropping it here
|
||||||
|
* instead would start the service with no session at all, which is the
|
||||||
|
* one outcome the service asking for a session cannot live with.
|
||||||
|
*/
|
||||||
|
static void pam_translate(cfg_t *sec, svc_t *svc, char *file)
|
||||||
|
{
|
||||||
|
const char *str;
|
||||||
|
|
||||||
|
str = sec_getstr(sec, "pam", NULL);
|
||||||
|
if (!str || !str[0])
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (strlcpy(svc->pam, str, sizeof(svc->pam)) >= sizeof(svc->pam)) {
|
||||||
|
logit(LOG_ERR, "%s: %s: pam '%s' is too long, not starting",
|
||||||
|
file, cfg_title(sec), str);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strchr(str, '/') || strstr(str, "..")) {
|
||||||
|
logit(LOG_ERR, "%s: %s: pam '%s' names a file in /etc/pam.d,"
|
||||||
|
" not a path, not starting", file, cfg_title(sec), str);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (svc->forking)
|
||||||
|
logit(LOG_WARNING, "%s: %s: pam with type = forking closes the"
|
||||||
|
" session when the initial process exits",
|
||||||
|
file, cfg_title(sec));
|
||||||
|
}
|
||||||
|
|
||||||
static void svc_translate(cfg_t *sec, int type, struct rlimit rlimit[], char *file)
|
static void svc_translate(cfg_t *sec, int type, struct rlimit rlimit[], char *file)
|
||||||
{
|
{
|
||||||
struct rlimit local_rlimit[RLIMIT_NLIMITS];
|
struct rlimit local_rlimit[RLIMIT_NLIMITS];
|
||||||
@@ -1699,6 +1735,7 @@ static void svc_translate(cfg_t *sec, int type, struct rlimit rlimit[], char *fi
|
|||||||
|
|
||||||
dirs_translate(sec, svc, file);
|
dirs_translate(sec, svc, file);
|
||||||
provides_translate(sec, svc, file);
|
provides_translate(sec, svc, file);
|
||||||
|
pam_translate(sec, svc, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
+6
-2
@@ -121,15 +121,19 @@ void unmount_tmpfs(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void unmount_regular(void)
|
int unmount_regular(void)
|
||||||
{
|
{
|
||||||
const struct mntent *mnt;
|
const struct mntent *mnt;
|
||||||
FILE *fp = NULL;
|
FILE *fp = NULL;
|
||||||
|
int rc = 0;
|
||||||
|
|
||||||
while ((mnt = iterator("/proc/mounts", &fp))) {
|
while ((mnt = iterator("/proc/mounts", &fp))) {
|
||||||
if (!unmount(mnt->mnt_dir))
|
rc = unmount(mnt->mnt_dir);
|
||||||
|
if (!rc)
|
||||||
iterator_end(&fp); /* Restart iteration */
|
iterator_end(&fp); /* Restart iteration */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return !!rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,398 @@
|
|||||||
|
/* Finit PAM session support
|
||||||
|
*
|
||||||
|
* Copyright (c) 2026 Joachim Wiberg <troglobit@gmail.com>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h" /* Generated by configure script */
|
||||||
|
|
||||||
|
#include <dirent.h> /* opendir(), readdir(), dirfd() */
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h> /* fcntl(), FD_CLOEXEC */
|
||||||
|
#include <grp.h> /* setgroups() */
|
||||||
|
#include <security/pam_appl.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdlib.h> /* malloc(), free(), strtol() */
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/prctl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "helpers.h"
|
||||||
|
#include "log.h"
|
||||||
|
#include "pam.h"
|
||||||
|
#include "svc.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A service has no terminal to prompt on and this path never
|
||||||
|
* authenticates, so a module that asks a question gets an error and its
|
||||||
|
* stack entry fails. For a non-interactive service that is the answer
|
||||||
|
* we want.
|
||||||
|
*/
|
||||||
|
static int pam_dialog(int num_msg, const struct pam_message **msg,
|
||||||
|
struct pam_response **resp, void *appdata)
|
||||||
|
{
|
||||||
|
return PAM_CONV_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct pam_conv conv = {
|
||||||
|
.conv = pam_dialog,
|
||||||
|
.appdata_ptr = NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The keeper never exec()s, so O_CLOEXEC does nothing for it, and it
|
||||||
|
* must not sit on Finit's event loop, api socket, inotify and
|
||||||
|
* notify-pipe descriptors for as long as the service lives. But PAM
|
||||||
|
* and its modules may have opened descriptors of their own while
|
||||||
|
* pam_open_session() ran, e.g. a pam_systemd/pam_elogind bus
|
||||||
|
* connection, a keyring fd, or a lock file, and those have to survive
|
||||||
|
* until pam_close_session() runs in the keeper. A blanket close of
|
||||||
|
* everything >= 3 cannot tell the two kinds apart, so take a snapshot
|
||||||
|
* of Finit's own fds before pam_start() opens anything of its own, and
|
||||||
|
* have the keeper close only those.
|
||||||
|
*
|
||||||
|
* Returns the number of fds found and sets *fdv to a malloc'd array of
|
||||||
|
* them, or -1 if /proc/self/fd could not be read, in which case the
|
||||||
|
* caller falls back to closing everything and PAM's own descriptors
|
||||||
|
* are lost along with Finit's.
|
||||||
|
*/
|
||||||
|
static int fdsnap(int **fdv)
|
||||||
|
{
|
||||||
|
struct dirent *d;
|
||||||
|
int *v;
|
||||||
|
int n, i;
|
||||||
|
DIR *dir;
|
||||||
|
int dfd;
|
||||||
|
|
||||||
|
dir = opendir("/proc/self/fd");
|
||||||
|
if (!dir)
|
||||||
|
return -1;
|
||||||
|
dfd = dirfd(dir);
|
||||||
|
|
||||||
|
n = 0;
|
||||||
|
while ((d = readdir(dir))) {
|
||||||
|
char *end;
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
fd = (int)strtol(d->d_name, &end, 10);
|
||||||
|
if (*end || fd < 3 || fd == dfd)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!n) {
|
||||||
|
closedir(dir);
|
||||||
|
*fdv = NULL;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
v = malloc(n * sizeof(*v));
|
||||||
|
if (!v) {
|
||||||
|
closedir(dir);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
rewinddir(dir);
|
||||||
|
|
||||||
|
i = 0;
|
||||||
|
while (i < n && (d = readdir(dir))) {
|
||||||
|
char *end;
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
fd = (int)strtol(d->d_name, &end, 10);
|
||||||
|
if (*end || fd < 3 || fd == dfd)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
v[i++] = fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
closedir(dir);
|
||||||
|
*fdv = v;
|
||||||
|
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Close exactly the fds fdsnap() found, leaving PAM's own alone. With
|
||||||
|
* no snapshot (nfd < 0), fall back to closing everything >= 3, PAM's
|
||||||
|
* descriptors lost along with Finit's.
|
||||||
|
*/
|
||||||
|
static void closefds(int *fdv, int nfd)
|
||||||
|
{
|
||||||
|
long max;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (nfd >= 0) {
|
||||||
|
for (i = 0; i < nfd; i++)
|
||||||
|
close(fdv[i]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
max = sysconf(_SC_OPEN_MAX);
|
||||||
|
#ifdef HAVE_CLOSE_RANGE
|
||||||
|
if (!close_range(3, ~0U, 0))
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
if (max < 0)
|
||||||
|
max = 1024;
|
||||||
|
for (i = 3; i < max; i++)
|
||||||
|
close(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The descriptors PAM opened have to survive in the keeper, but not in
|
||||||
|
* the process that goes on to exec() the service: a bus connection, a
|
||||||
|
* keyring fd or a lock file the stack opened would otherwise be
|
||||||
|
* inherited by the daemon, and by the logit helper forked next to it,
|
||||||
|
* for as long as the service lives. Finit's own fds are O_CLOEXEC,
|
||||||
|
* PAM's are not reliably, so diff a fresh snapshot against the one
|
||||||
|
* taken before pam_start() and mark everything new close-on-exec. The
|
||||||
|
* keeper is unaffected, it was forked already and has its own
|
||||||
|
* descriptor table.
|
||||||
|
*
|
||||||
|
* With no snapshot to diff against (nfd < 0) there is nothing to do
|
||||||
|
* here, telling Finit's fds from PAM's is exactly what the snapshot is
|
||||||
|
* for.
|
||||||
|
*/
|
||||||
|
static void cloexec_pamfds(const char *id, int *fdv, int nfd)
|
||||||
|
{
|
||||||
|
int *now = NULL;
|
||||||
|
int i, j, n;
|
||||||
|
|
||||||
|
if (nfd < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
n = fdsnap(&now);
|
||||||
|
if (n < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (i = 0; i < n; i++) {
|
||||||
|
int flags, old = 0;
|
||||||
|
|
||||||
|
for (j = 0; j < nfd; j++) {
|
||||||
|
if (now[i] == fdv[j]) {
|
||||||
|
old = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (old)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
flags = fcntl(now[i], F_GETFD);
|
||||||
|
if (flags == -1 || fcntl(now[i], F_SETFD, flags | FD_CLOEXEC))
|
||||||
|
logit(LOG_WARNING, "%s: pam: failed FD_CLOEXEC on fd %d,"
|
||||||
|
" the service inherits it", id, now[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(now);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* pam_getenvlist() hands back a NUL-terminated array of malloc'd
|
||||||
|
* "NAME=value" strings. Free all of it so a failure past that point
|
||||||
|
* never leaves pamsess_open() returning a dangling *envp.
|
||||||
|
*/
|
||||||
|
static void free_envlist(char **envp)
|
||||||
|
{
|
||||||
|
char **e;
|
||||||
|
|
||||||
|
if (!envp)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (e = envp; *e; e++)
|
||||||
|
free(*e);
|
||||||
|
free(envp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Holds the PAM handle for the lifetime of the service and closes the
|
||||||
|
* session when it dies. The handle was created before the fork, so
|
||||||
|
* pam_end() gets PAM_DATA_SILENT: this process is a copy, the modules
|
||||||
|
* must not run their full cleanup twice.
|
||||||
|
*/
|
||||||
|
static void keeper(pam_handle_t *h, uid_t uid, gid_t gid, pid_t ppid,
|
||||||
|
sigset_t *mask, const char *id, int *fdv, int nfd)
|
||||||
|
{
|
||||||
|
int rc, sig;
|
||||||
|
|
||||||
|
/* Finit's syslog socket is one of the fds about to be closed;
|
||||||
|
* close it here first so syslog(3) knows to reconnect instead
|
||||||
|
* of writing to a fd pulled out from under it. */
|
||||||
|
closelog();
|
||||||
|
closefds(fdv, nfd);
|
||||||
|
setprocnm("(finit-pam)");
|
||||||
|
|
||||||
|
/* No groups are needed to close a session */
|
||||||
|
if (setgroups(0, NULL))
|
||||||
|
warn("%s: pam: failed setgroups()", id);
|
||||||
|
if (setgid(gid))
|
||||||
|
warn("%s: pam: failed setgid(%d)", id, gid);
|
||||||
|
if (setuid(uid))
|
||||||
|
warn("%s: pam: failed setuid(%d)", id, uid);
|
||||||
|
|
||||||
|
/* After the credential change, which clears a pending pdeath signal */
|
||||||
|
if (prctl(PR_SET_PDEATHSIG, SIGTERM, 0, 0, 0))
|
||||||
|
warn("%s: pam: failed PR_SET_PDEATHSIG", id);
|
||||||
|
|
||||||
|
/* The service may have exited already, then no signal is coming.
|
||||||
|
* Anything but success means SIGTERM has not arrived yet, so keep
|
||||||
|
* waiting: closing the session under a service that is still
|
||||||
|
* running would leave nobody to close it later. glibc reports
|
||||||
|
* the error number as the return value and musl returns -1 with
|
||||||
|
* errno set, hence the test on rc alone. The mask is static, so
|
||||||
|
* a failure that never clears is not reachable here. */
|
||||||
|
if (getppid() == ppid) {
|
||||||
|
do {
|
||||||
|
rc = sigwait(mask, &sig);
|
||||||
|
} while (rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = pam_setcred(h, PAM_DELETE_CRED | PAM_SILENT);
|
||||||
|
if (rc != PAM_SUCCESS)
|
||||||
|
logit(LOG_WARNING, "%s: pam: failed pam_setcred(DELETE_CRED): %s",
|
||||||
|
id, pam_strerror(h, rc));
|
||||||
|
|
||||||
|
rc = pam_close_session(h, PAM_SILENT);
|
||||||
|
if (rc != PAM_SUCCESS)
|
||||||
|
logit(LOG_WARNING, "%s: pam: failed pam_close_session(): %s",
|
||||||
|
id, pam_strerror(h, rc));
|
||||||
|
|
||||||
|
pam_end(h, rc | PAM_DATA_SILENT);
|
||||||
|
_exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Open a PAM session for svc, as root, before privileges are dropped.
|
||||||
|
* On success a keeper process has been forked and *envp holds the
|
||||||
|
* session environment for the caller to apply. The process that goes
|
||||||
|
* on to exec() must not call pam_end(), its handle disappears with the
|
||||||
|
* exec; only the keeper ever closes the session. *envp is only ever
|
||||||
|
* non-NULL on success.
|
||||||
|
*/
|
||||||
|
int pamsess_open(svc_t *svc, uid_t uid, gid_t gid, char ***envp)
|
||||||
|
{
|
||||||
|
char *id = svc_ident(svc, NULL, 0);
|
||||||
|
pam_handle_t *h = NULL;
|
||||||
|
const char *user;
|
||||||
|
sigset_t mask, omask;
|
||||||
|
pid_t pid, ppid;
|
||||||
|
int *fdv = NULL;
|
||||||
|
int rc, nfd;
|
||||||
|
const char *why;
|
||||||
|
|
||||||
|
*envp = NULL;
|
||||||
|
|
||||||
|
/* Every fork funnels through here, service_start() only guards
|
||||||
|
* the daemon itself, so this is where a value that must not
|
||||||
|
* reach pam_start() is stopped. */
|
||||||
|
why = pam_invalid(svc->pam);
|
||||||
|
if (why) {
|
||||||
|
logit(LOG_ERR, "%s: pam '%s' %s", id, svc->pam, why);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Snapshot Finit's own fds before pam_start() opens anything */
|
||||||
|
nfd = fdsnap(&fdv);
|
||||||
|
|
||||||
|
user = svc->username[0] ? svc->username : "root";
|
||||||
|
|
||||||
|
rc = pam_start(svc->pam, user, &conv, &h);
|
||||||
|
if (rc != PAM_SUCCESS) {
|
||||||
|
logit(LOG_ERR, "%s: failed pam_start(%s, %s): %s", id,
|
||||||
|
svc->pam, user, pam_strerror(h, rc));
|
||||||
|
free(fdv);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (svc_has_ctty(svc)) {
|
||||||
|
rc = pam_set_item(h, PAM_TTY, svc->log.ctty);
|
||||||
|
if (rc != PAM_SUCCESS)
|
||||||
|
logit(LOG_WARNING, "%s: failed pam_set_item(PAM_TTY, %s): %s",
|
||||||
|
id, svc->log.ctty, pam_strerror(h, rc));
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = pam_acct_mgmt(h, PAM_SILENT);
|
||||||
|
if (rc != PAM_SUCCESS) {
|
||||||
|
logit(LOG_ERR, "%s: account denied by %s: %s", id, svc->pam,
|
||||||
|
pam_strerror(h, rc));
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = pam_setcred(h, PAM_ESTABLISH_CRED | PAM_SILENT);
|
||||||
|
if (rc != PAM_SUCCESS) {
|
||||||
|
logit(LOG_ERR, "%s: failed pam_setcred(%s): %s", id, svc->pam,
|
||||||
|
pam_strerror(h, rc));
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = pam_open_session(h, PAM_SILENT);
|
||||||
|
if (rc != PAM_SUCCESS) {
|
||||||
|
logit(LOG_ERR, "%s: failed pam_open_session(%s): %s", id,
|
||||||
|
svc->pam, pam_strerror(h, rc));
|
||||||
|
goto cred;
|
||||||
|
}
|
||||||
|
|
||||||
|
*envp = pam_getenvlist(h);
|
||||||
|
|
||||||
|
/* Block SIGTERM before forking, the keeper must not miss it */
|
||||||
|
sigemptyset(&mask);
|
||||||
|
sigaddset(&mask, SIGTERM);
|
||||||
|
sigprocmask(SIG_BLOCK, &mask, &omask);
|
||||||
|
|
||||||
|
/* Forked here, before the service child calls setsid(), so the
|
||||||
|
* keeper stays in Finit's process group. Teardown does
|
||||||
|
* kill(-svc->pid, SIGKILL) on the service's group, and a keeper
|
||||||
|
* that had ended up there would be killed with the service,
|
||||||
|
* before it ever gets to close the session. */
|
||||||
|
ppid = getpid();
|
||||||
|
pid = fork();
|
||||||
|
if (pid < 0) {
|
||||||
|
err(1, "%s: failed forking off PAM session keeper", id);
|
||||||
|
sigprocmask(SIG_SETMASK, &omask, NULL);
|
||||||
|
pam_close_session(h, PAM_SILENT);
|
||||||
|
free_envlist(*envp);
|
||||||
|
*envp = NULL;
|
||||||
|
goto cred;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pid)
|
||||||
|
keeper(h, uid, gid, ppid, &mask, id, fdv, nfd); /* does not return */
|
||||||
|
|
||||||
|
sigprocmask(SIG_SETMASK, &omask, NULL);
|
||||||
|
cloexec_pamfds(id, fdv, nfd);
|
||||||
|
free(fdv);
|
||||||
|
dbg("%s: PAM session %s open, keeper is PID %d", id, svc->pam, pid);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
cred:
|
||||||
|
pam_setcred(h, PAM_DELETE_CRED | PAM_SILENT);
|
||||||
|
fail:
|
||||||
|
free(fdv);
|
||||||
|
pam_end(h, rc);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Local Variables:
|
||||||
|
* indent-tabs-mode: t
|
||||||
|
* c-file-style: "linux"
|
||||||
|
* End:
|
||||||
|
*/
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/* Finit PAM session support
|
||||||
|
*
|
||||||
|
* Copyright (c) 2026 Joachim Wiberg <troglobit@gmail.com>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef FINIT_PAM_H_
|
||||||
|
#define FINIT_PAM_H_
|
||||||
|
|
||||||
|
#include <string.h> /* strchr(), strlen(), strstr() */
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#include "svc.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A pam value names a file in /etc/pam.d, it is not a path, and conf.c
|
||||||
|
* stores an over-long value truncated, so a value filling the buffer
|
||||||
|
* must not be passed on as some other, shorter name that happens to
|
||||||
|
* exist. Both users share this one definition: service_start(), which
|
||||||
|
* refuses the service outright, log line and a visible state, and
|
||||||
|
* pamsess_open(), which every fork funnels through, including the pre:,
|
||||||
|
* post:, ready:, cleanup:, stop and reload scripts that run before
|
||||||
|
* service_start() ever does.
|
||||||
|
*
|
||||||
|
* Returns why the value cannot be used, or NULL when it can.
|
||||||
|
*/
|
||||||
|
static inline const char *pam_invalid(const char *pam)
|
||||||
|
{
|
||||||
|
if (!pam[0])
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (strlen(pam) >= MAX_ARG_LEN - 1)
|
||||||
|
return "is too long";
|
||||||
|
|
||||||
|
if (strchr(pam, '/') || strstr(pam, ".."))
|
||||||
|
return "names a file in /etc/pam.d, not a path";
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pamsess_open(svc_t *svc, uid_t uid, gid_t gid, char ***envp);
|
||||||
|
|
||||||
|
#endif /* FINIT_PAM_H_ */
|
||||||
+105
-5
@@ -54,6 +54,7 @@
|
|||||||
#include "devmon.h"
|
#include "devmon.h"
|
||||||
#include "finit.h"
|
#include "finit.h"
|
||||||
#include "helpers.h"
|
#include "helpers.h"
|
||||||
|
#include "pam.h"
|
||||||
#include "pid.h"
|
#include "pid.h"
|
||||||
#include "private.h"
|
#include "private.h"
|
||||||
#include "sig.h"
|
#include "sig.h"
|
||||||
@@ -544,10 +545,22 @@ static void set_uid(uid_t uid, svc_t *svc)
|
|||||||
*/
|
*/
|
||||||
if (svc->capabilities[0]) {
|
if (svc->capabilities[0]) {
|
||||||
cap_iab_t cap_iab;
|
cap_iab_t cap_iab;
|
||||||
|
#ifdef HAVE_LIBPAM
|
||||||
|
cap_iab_t pam_iab = NULL;
|
||||||
|
cap_value_t c;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A module like pam_cap.so may have granted ambient
|
||||||
|
* capabilities. Capture them while we are still root,
|
||||||
|
* cap_setuid() below clears them.
|
||||||
|
*/
|
||||||
|
if (svc->pam[0])
|
||||||
|
pam_iab = cap_iab_get_proc();
|
||||||
|
#endif
|
||||||
|
|
||||||
if (cap_setuid(uid)) {
|
if (cap_setuid(uid)) {
|
||||||
err(1, "%s: failed cap_setuid(%d)", svc_ident(svc, NULL, 0), uid);
|
err(1, "%s: failed cap_setuid(%d)", svc_ident(svc, NULL, 0), uid);
|
||||||
return;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* After dropping privileges, set the specific capabilities we need */
|
/* After dropping privileges, set the specific capabilities we need */
|
||||||
@@ -555,15 +568,30 @@ static void set_uid(uid_t uid, svc_t *svc)
|
|||||||
if (!cap_iab) {
|
if (!cap_iab) {
|
||||||
err(1, "%s: failed parsing capabilities '%s'",
|
err(1, "%s: failed parsing capabilities '%s'",
|
||||||
svc_ident(svc, NULL, 0), svc->capabilities);
|
svc_ident(svc, NULL, 0), svc->capabilities);
|
||||||
return;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_LIBPAM
|
||||||
|
/* Merge, do not overwrite, what PAM granted */
|
||||||
|
if (pam_iab) {
|
||||||
|
for (c = 0; c <= CAP_LAST_CAP; c++) {
|
||||||
|
if (cap_iab_get_vector(pam_iab, CAP_IAB_AMB, c))
|
||||||
|
cap_iab_set_vector(cap_iab, CAP_IAB_AMB, c, CAP_SET);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (cap_iab_set_proc(cap_iab) != 0) {
|
if (cap_iab_set_proc(cap_iab) != 0) {
|
||||||
cap_free(cap_iab);
|
cap_free(cap_iab);
|
||||||
err(1, "%s: failed setting capabilities",
|
err(1, "%s: failed setting capabilities",
|
||||||
svc_ident(svc, NULL, 0));
|
svc_ident(svc, NULL, 0));
|
||||||
}
|
}
|
||||||
cap_free(cap_iab);
|
cap_free(cap_iab);
|
||||||
|
out:
|
||||||
|
#ifdef HAVE_LIBPAM
|
||||||
|
cap_free(pam_iab);
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
} else
|
} else
|
||||||
#endif
|
#endif
|
||||||
if (setuid(uid))
|
if (setuid(uid))
|
||||||
@@ -703,6 +731,9 @@ static pid_t service_fork(svc_t *svc)
|
|||||||
|
|
||||||
if (pid == 0) {
|
if (pid == 0) {
|
||||||
char *home = NULL;
|
char *home = NULL;
|
||||||
|
#ifdef HAVE_LIBPAM
|
||||||
|
char **pam_env = NULL;
|
||||||
|
#endif
|
||||||
#ifdef ENABLE_STATIC
|
#ifdef ENABLE_STATIC
|
||||||
int uid = 0; /* XXX: Fix better warning that dropprivs is disabled. */
|
int uid = 0; /* XXX: Fix better warning that dropprivs is disabled. */
|
||||||
int gid = 0;
|
int gid = 0;
|
||||||
@@ -735,6 +766,15 @@ static pid_t service_fork(svc_t *svc)
|
|||||||
svc_ident(svc, NULL, 0), rlim2str(i));
|
svc_ident(svc, NULL, 0), rlim2str(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_LIBPAM
|
||||||
|
/*
|
||||||
|
* After our own rlimits so pam_limits wins, and before
|
||||||
|
* any credential change: the session stack needs root.
|
||||||
|
*/
|
||||||
|
if (svc->pam[0] && pamsess_open(svc, uid, gid, &pam_env))
|
||||||
|
_exit(EX_OSERR);
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef ENABLE_STATIC
|
#ifndef ENABLE_STATIC
|
||||||
/* Set supplementary groups from /etc/group and config */
|
/* Set supplementary groups from /etc/group and config */
|
||||||
{
|
{
|
||||||
@@ -797,6 +837,28 @@ static pid_t service_fork(svc_t *svc)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_LIBPAM
|
||||||
|
/* pam_env after our defaults, before env:file */
|
||||||
|
if (pam_env) {
|
||||||
|
const char *pam_home;
|
||||||
|
|
||||||
|
for (int i = 0; pam_env[i]; i++)
|
||||||
|
putenv(pam_env[i]);
|
||||||
|
|
||||||
|
/* pam_env has the last word on HOME, so the
|
||||||
|
* working directory has to follow it, the chdir()
|
||||||
|
* above used the passwd home. */
|
||||||
|
pam_home = getenv("HOME");
|
||||||
|
if (pam_home && (!home || strcmp(pam_home, home))) {
|
||||||
|
if (chdir(pam_home)) {
|
||||||
|
if (chdir("/"))
|
||||||
|
err(1, "%s: failed chdir(%s) and chdir(/)",
|
||||||
|
svc_ident(svc, NULL, 0), pam_home);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Source any environment from env:/path/to/file */
|
/* Source any environment from env:/path/to/file */
|
||||||
source_env(svc);
|
source_env(svc);
|
||||||
}
|
}
|
||||||
@@ -811,6 +873,26 @@ static pid_t service_fork(svc_t *svc)
|
|||||||
return pid;
|
return pid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Checked here, next to the other preconditions, and not only in
|
||||||
|
* conf.c, because a .conf reload rewrites the service and would
|
||||||
|
* otherwise let a refused value start it after all. Running without
|
||||||
|
* the session the service asked for costs it pam_limits, its private
|
||||||
|
* /tmp, and its logind session, with nothing said.
|
||||||
|
*/
|
||||||
|
static int pam_refused(svc_t *svc)
|
||||||
|
{
|
||||||
|
const char *why = pam_invalid(svc->pam);
|
||||||
|
|
||||||
|
if (!why)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
logit(LOG_ERR, "%s: pam '%s' %s, not starting",
|
||||||
|
svc_ident(svc, NULL, 0), svc->pam, why);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* service_start - Start service
|
* service_start - Start service
|
||||||
* @svc: Service to start
|
* @svc: Service to start
|
||||||
@@ -854,6 +936,20 @@ static int service_start(svc_t *svc)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pam_refused(svc)) {
|
||||||
|
svc_missing(svc);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef HAVE_LIBPAM
|
||||||
|
if (svc->pam[0]) {
|
||||||
|
logit(LOG_ERR, "%s: pam %s requires Finit built with --enable-pam",
|
||||||
|
svc_ident(svc, NULL, 0), svc->pam);
|
||||||
|
svc_missing(svc);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (svc_is_tty(svc) && !svc->notty) {
|
if (svc_is_tty(svc) && !svc->notty) {
|
||||||
char *dev = tty_canonicalize(svc->dev);
|
char *dev = tty_canonicalize(svc->dev);
|
||||||
|
|
||||||
@@ -2505,6 +2601,7 @@ svc_t *service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
|
|||||||
memset(svc->capabilities, 0, sizeof(svc->capabilities));
|
memset(svc->capabilities, 0, sizeof(svc->capabilities));
|
||||||
|
|
||||||
/* block format only, set by conf.c after registration */
|
/* block format only, set by conf.c after registration */
|
||||||
|
memset(svc->pam, 0, sizeof(svc->pam));
|
||||||
for (int i = 0; i < NUM_SVCDIRS; i++) {
|
for (int i = 0; i < NUM_SVCDIRS; i++) {
|
||||||
memset((char *)svc + svcdirs[i].off, 0, MAX_ARG_LEN);
|
memset((char *)svc + svcdirs[i].off, 0, MAX_ARG_LEN);
|
||||||
svc->dir_mode[i] = 0755;
|
svc->dir_mode[i] = 0755;
|
||||||
@@ -2606,11 +2703,14 @@ svc_t *service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
|
|||||||
if (cgroup)
|
if (cgroup)
|
||||||
parse_cgroup(svc, cgroup);
|
parse_cgroup(svc, cgroup);
|
||||||
|
|
||||||
/* New, recently modified or unchanged ... used on reload. */
|
/*
|
||||||
|
* New or modified since the last reload. The mark is cleared when
|
||||||
|
* the change has been applied, on start or reload, so one that is
|
||||||
|
* still set here is a change that has not been applied yet, e.g.
|
||||||
|
* the service is paused waiting for a condition. Leave it.
|
||||||
|
*/
|
||||||
if ((file && conf_changed(file)) || conf_changed(svc_getenv(svc)) || svc->args_dirty)
|
if ((file && conf_changed(file)) || conf_changed(svc_getenv(svc)) || svc->args_dirty)
|
||||||
svc_mark_dirty(svc);
|
svc_mark_dirty(svc);
|
||||||
else
|
|
||||||
svc_mark_clean(svc);
|
|
||||||
|
|
||||||
svc_enable(svc);
|
svc_enable(svc);
|
||||||
|
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ static struct sigmap {
|
|||||||
|
|
||||||
void mdadm_wait(void);
|
void mdadm_wait(void);
|
||||||
void unmount_tmpfs(void);
|
void unmount_tmpfs(void);
|
||||||
void unmount_regular(void);
|
int unmount_regular(void);
|
||||||
|
|
||||||
static void fs_swapoff(void)
|
static void fs_swapoff(void)
|
||||||
{
|
{
|
||||||
@@ -302,6 +302,7 @@ void do_shutdown(shutop_t op)
|
|||||||
struct sched_param sched_param = { .sched_priority = 99 };
|
struct sched_param sched_param = { .sched_priority = 99 };
|
||||||
int in_cont = in_container();
|
int in_cont = in_container();
|
||||||
int signo = SIGTERM;
|
int signo = SIGTERM;
|
||||||
|
int rc;
|
||||||
|
|
||||||
if (SHUTDOWN_DEBUG) cprintf("do_shutdown: entered, op=%d\n", op);
|
if (SHUTDOWN_DEBUG) cprintf("do_shutdown: entered, op=%d\n", op);
|
||||||
|
|
||||||
@@ -377,12 +378,13 @@ void do_shutdown(shutop_t op)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Unmount any tmpfs before unmounting swap ... */
|
/* Unmount any tmpfs before unmounting swap ... */
|
||||||
print(0, "Unmounting filesystems ...");
|
print(-1, "Unmounting filesystems ...");
|
||||||
unmount_tmpfs();
|
unmount_tmpfs();
|
||||||
fs_swapoff();
|
fs_swapoff();
|
||||||
|
|
||||||
/* ... unmount remaining regular file systems. */
|
/* ... unmount remaining regular file systems. */
|
||||||
unmount_regular();
|
rc = unmount_regular();
|
||||||
|
print(rc, NULL);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We sit on / so we must remount it ro, try all the things!
|
* We sit on / so we must remount it ro, try all the things!
|
||||||
@@ -391,9 +393,9 @@ void do_shutdown(shutop_t op)
|
|||||||
* - https://bugs.launchpad.net/ubuntu/+source/util-linux/+bug/29187
|
* - https://bugs.launchpad.net/ubuntu/+source/util-linux/+bug/29187
|
||||||
*/
|
*/
|
||||||
sync();
|
sync();
|
||||||
run("mount -n -o remount,ro -t dummytype dummydev /", NULL);
|
if (run("mount -n -o remount,ro -t dummytype dummydev /", NULL) &&
|
||||||
run("mount -n -o remount,ro dummydev /", NULL);
|
run("mount -n -o remount,ro dummydev /", NULL))
|
||||||
run("mount -n -o remount,ro /", "mount");
|
run("mount -n -o remount,ro /", "mount");
|
||||||
|
|
||||||
/* Call mdadm to mark any RAID array(s) as clean before halting. */
|
/* Call mdadm to mark any RAID array(s) as clean before halting. */
|
||||||
mdadm_wait();
|
mdadm_wait();
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ typedef enum {
|
|||||||
#define MAX_IDENT_LEN (MAX_ARG_LEN + MAX_ID_LEN + 1)
|
#define MAX_IDENT_LEN (MAX_ARG_LEN + MAX_ID_LEN + 1)
|
||||||
#define MAX_STR_LEN 64
|
#define MAX_STR_LEN 64
|
||||||
#define MAX_COND_LEN (MAX_ARG_LEN * 3)
|
#define MAX_COND_LEN (MAX_ARG_LEN * 3)
|
||||||
#define MAX_USER_LEN 16
|
#define MAX_USER_LEN MAX_ARG_LEN
|
||||||
#define MAX_NUM_SUPGROUPS 4
|
#define MAX_NUM_SUPGROUPS 4
|
||||||
#define MAX_NUM_PROVIDES 4
|
#define MAX_NUM_PROVIDES 4
|
||||||
#define MAX_NUM_FDS 64 /* Max number of I/O plugins */
|
#define MAX_NUM_FDS 64 /* Max number of I/O plugins */
|
||||||
@@ -203,6 +203,7 @@ typedef struct svc {
|
|||||||
char supgroups[MAX_NUM_SUPGROUPS][MAX_USER_LEN];
|
char supgroups[MAX_NUM_SUPGROUPS][MAX_USER_LEN];
|
||||||
int num_supgroups;
|
int num_supgroups;
|
||||||
char capabilities[MAX_CMD_LEN];
|
char capabilities[MAX_CMD_LEN];
|
||||||
|
char pam[MAX_ARG_LEN]; /* /etc/pam.d/NAME, block format only */
|
||||||
|
|
||||||
/* Directories set up for the service, block format only, the
|
/* Directories set up for the service, block format only, the
|
||||||
* name is resolved under a fixed base, e.g. /run/NAME */
|
* name is resolved under a fixed base, e.g. /run/NAME */
|
||||||
|
|||||||
+50
-42
@@ -24,6 +24,7 @@
|
|||||||
#include "config.h" /* Generated by configure script */
|
#include "config.h" /* Generated by configure script */
|
||||||
|
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
|
#include <fcntl.h>
|
||||||
#include <ftw.h>
|
#include <ftw.h>
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
#include <glob.h>
|
#include <glob.h>
|
||||||
@@ -557,67 +558,74 @@ static void tmpfiles(char *line)
|
|||||||
if (gid < 0)
|
if (gid < 0)
|
||||||
gid = 0;
|
gid = 0;
|
||||||
|
|
||||||
rc = makedir(path, mode ?: 0755);
|
if (!mode)
|
||||||
if (rc && errno == EEXIST)
|
mode = 0755;
|
||||||
rc = chmod(path, mode ?: 0755);
|
rc = makedir(path, mode);
|
||||||
if (chown(path, uid, gid))
|
if (!rc && dirperm(path, mode, uid, gid))
|
||||||
warn("Failed chown(%s, %d, %d)", path, uid, gid);
|
warn("Failed setting mode/owner on %s", path);
|
||||||
}
|
}
|
||||||
umask(omask);
|
umask(omask);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'e':
|
case 'e': {
|
||||||
|
int uid, gid;
|
||||||
|
|
||||||
if (glob(path, GLOB_NOESCAPE, NULL, &gl))
|
if (glob(path, GLOB_NOESCAPE, NULL, &gl))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
uid = parse_uid(user);
|
||||||
|
gid = parse_gid(group);
|
||||||
|
if (gid < 0)
|
||||||
|
gid = 0;
|
||||||
|
|
||||||
for (size_t i = 0; i < gl.gl_pathc; i++) {
|
for (size_t i = 0; i < gl.gl_pathc; i++) {
|
||||||
char *p = gl.gl_pathv[i];
|
char *p = gl.gl_pathv[i];
|
||||||
int uid, gid;
|
|
||||||
|
|
||||||
/* e only adjusts existing directories */
|
/* skip non-directories */
|
||||||
if (!fisdir(p))
|
if (dirperm(p, mode, uid, gid) && errno != ENOTDIR)
|
||||||
continue;
|
warn("Failed setting mode/owner on %s", p);
|
||||||
|
|
||||||
uid = parse_uid(user);
|
|
||||||
gid = parse_gid(group);
|
|
||||||
if (gid < 0)
|
|
||||||
gid = 0;
|
|
||||||
|
|
||||||
if (mode)
|
|
||||||
chmod(p, mode);
|
|
||||||
if (uid >= 0 && chown(p, uid, gid))
|
|
||||||
warn("Failed chown(%s, %d, %d)", p, uid, gid);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case 'f':
|
case 'f':
|
||||||
case 'F':
|
case 'F': {
|
||||||
mkparent(path, 0755);
|
int flags = O_WRONLY | O_CREAT | O_NOFOLLOW | O_CLOEXEC;
|
||||||
|
int fd, uid, gid;
|
||||||
|
|
||||||
if (type[1] == '+' || type[0] == 'F') {
|
if (type[1] == '+' || type[0] == 'F') {
|
||||||
/* f+/F will create or truncate the file */
|
flags |= O_TRUNC; /* create or truncate */
|
||||||
fp = fopen(path, "w+");
|
|
||||||
} else {
|
} else {
|
||||||
/* f will create the file if it doesn't exist */
|
if (!strc)
|
||||||
if (strc)
|
break; /* create only if missing */
|
||||||
fp = fopen(path, "w");
|
flags |= O_EXCL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fp) {
|
mkparent(path, 0755);
|
||||||
int uid, gid;
|
fd = open(path, flags, 0666);
|
||||||
|
if (fd < 0) {
|
||||||
write_arg(fp, arg);
|
if (errno != EEXIST)
|
||||||
rc = fclose(fp);
|
warn("Failed creating %s", path);
|
||||||
|
break;
|
||||||
/* Apply mode and ownership */
|
|
||||||
if (mode)
|
|
||||||
chmod(path, mode);
|
|
||||||
uid = parse_uid(user);
|
|
||||||
gid = parse_gid(group);
|
|
||||||
if (gid < 0)
|
|
||||||
gid = 0;
|
|
||||||
if (uid >= 0 && chown(path, uid, gid))
|
|
||||||
warn("Failed chown(%s, %d, %d)", path, uid, gid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fp = fdopen(fd, "w");
|
||||||
|
if (!fp) {
|
||||||
|
close(fd);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
write_arg(fp, arg);
|
||||||
|
|
||||||
|
uid = parse_uid(user);
|
||||||
|
gid = parse_gid(group);
|
||||||
|
if (gid < 0)
|
||||||
|
gid = 0;
|
||||||
|
if (fdperm(fd, mode, uid, gid))
|
||||||
|
warn("Failed setting mode/owner on %s", path);
|
||||||
|
|
||||||
|
rc = fclose(fp);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case 'l': /* Finit extension, like 'L' but only if target exists */
|
case 'l': /* Finit extension, like 'L' but only if target exists */
|
||||||
if (!arg) {
|
if (!arg) {
|
||||||
paste(buf, sizeof(buf), "/usr/share/factory", path);
|
paste(buf, sizeof(buf), "/usr/share/factory", path);
|
||||||
|
|||||||
+40
-5
@@ -25,6 +25,7 @@
|
|||||||
|
|
||||||
#include <ctype.h> /* isprint() */
|
#include <ctype.h> /* isprint() */
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
#include <grp.h>
|
#include <grp.h>
|
||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
#ifdef HAVE_MNTENT_H
|
#ifdef HAVE_MNTENT_H
|
||||||
@@ -374,6 +375,43 @@ int rmrf(const char *path)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set mode and owner on an open fd, only what differs is touched, the
|
||||||
|
* file may be immutable. mode 0 skips the chmod, uid -1 the chown.
|
||||||
|
*/
|
||||||
|
int fdperm(int fd, mode_t mode, uid_t uid, gid_t gid)
|
||||||
|
{
|
||||||
|
struct stat st;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
rc = fstat(fd, &st);
|
||||||
|
if (!rc && mode && (st.st_mode & 07777) != mode)
|
||||||
|
rc = fchmod(fd, mode);
|
||||||
|
if (!rc && uid != (uid_t)-1 && (st.st_uid != uid || st.st_gid != gid))
|
||||||
|
rc = fchown(fd, uid, gid);
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Same for a directory that may already exist. Opened with
|
||||||
|
* O_NOFOLLOW | O_DIRECTORY, so the change lands on the directory
|
||||||
|
* itself and not on whatever a link at @path points to.
|
||||||
|
*/
|
||||||
|
int dirperm(const char *path, mode_t mode, uid_t uid, gid_t gid)
|
||||||
|
{
|
||||||
|
int fd, rc;
|
||||||
|
|
||||||
|
fd = open(path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
|
||||||
|
if (fd < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
rc = fdperm(fd, mode, uid, gid);
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Like mksubsys() but with the ids already resolved, uid -1 skips the
|
* Like mksubsys() but with the ids already resolved, uid -1 skips the
|
||||||
* chown. Parents are created 0755, only the leaf gets @mode.
|
* chown. Parents are created 0755, only the leaf gets @mode.
|
||||||
@@ -386,11 +424,8 @@ int mksubsysd(const char *dir, mode_t mode, uid_t uid, gid_t gid)
|
|||||||
omask = umask(0);
|
omask = umask(0);
|
||||||
|
|
||||||
rc = mkpath(dir, 0755);
|
rc = mkpath(dir, 0755);
|
||||||
if (!rc) {
|
if (!rc && dirperm(dir, mode, uid, gid))
|
||||||
rc = chmod(dir, mode);
|
warn("Failed setting mode/owner on %s", dir);
|
||||||
if (!rc && uid != (uid_t)-1 && chown(dir, uid, gid))
|
|
||||||
err(1, "Failed chown(%s, %d, %d)", dir, (int)uid, (int)gid);
|
|
||||||
}
|
|
||||||
|
|
||||||
umask(omask);
|
umask(omask);
|
||||||
|
|
||||||
|
|||||||
@@ -71,6 +71,8 @@ int getgroup (const char *group);
|
|||||||
int getcuser (char *buf, size_t len);
|
int getcuser (char *buf, size_t len);
|
||||||
int getcgroup (char *buf, size_t len);
|
int getcgroup (char *buf, size_t len);
|
||||||
|
|
||||||
|
int fdperm (int fd, mode_t mode, uid_t uid, gid_t gid);
|
||||||
|
int dirperm (const char *path, mode_t mode, uid_t uid, gid_t gid);
|
||||||
int mksubsys (const char *dir, mode_t mode, char *user, char *group);
|
int mksubsys (const char *dir, mode_t mode, char *user, char *group);
|
||||||
int mksubsysd (const char *dir, mode_t mode, uid_t uid, gid_t gid);
|
int mksubsysd (const char *dir, mode_t mode, uid_t uid, gid_t gid);
|
||||||
int chownr (const char *path, uid_t uid, gid_t gid);
|
int chownr (const char *path, uid_t uid, gid_t gid);
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ EXTRA_DIST += conf-template.sh
|
|||||||
EXTRA_DIST += script-timeout.sh
|
EXTRA_DIST += script-timeout.sh
|
||||||
EXTRA_DIST += crashing.sh
|
EXTRA_DIST += crashing.sh
|
||||||
EXTRA_DIST += dep-chain-reload.sh
|
EXTRA_DIST += dep-chain-reload.sh
|
||||||
|
EXTRA_DIST += reload-while-paused.sh
|
||||||
EXTRA_DIST += depserv.sh
|
EXTRA_DIST += depserv.sh
|
||||||
EXTRA_DIST += devmon.sh
|
EXTRA_DIST += devmon.sh
|
||||||
EXTRA_DIST += failing-sysv.sh
|
EXTRA_DIST += failing-sysv.sh
|
||||||
@@ -51,6 +52,7 @@ EXTRA_DIST += global-envs.sh
|
|||||||
EXTRA_DIST += initctl-status-subset.sh
|
EXTRA_DIST += initctl-status-subset.sh
|
||||||
EXTRA_DIST += keventd.sh
|
EXTRA_DIST += keventd.sh
|
||||||
EXTRA_DIST += notify.sh
|
EXTRA_DIST += notify.sh
|
||||||
|
EXTRA_DIST += pam-session.sh
|
||||||
EXTRA_DIST += pidfile.sh
|
EXTRA_DIST += pidfile.sh
|
||||||
EXTRA_DIST += stale-pidfile.sh
|
EXTRA_DIST += stale-pidfile.sh
|
||||||
EXTRA_DIST += pre-post-serv.sh
|
EXTRA_DIST += pre-post-serv.sh
|
||||||
@@ -108,6 +110,7 @@ TESTS += conf-template.sh
|
|||||||
TESTS += script-timeout.sh
|
TESTS += script-timeout.sh
|
||||||
TESTS += crashing.sh
|
TESTS += crashing.sh
|
||||||
TESTS += dep-chain-reload.sh
|
TESTS += dep-chain-reload.sh
|
||||||
|
TESTS += reload-while-paused.sh
|
||||||
TESTS += depserv.sh
|
TESTS += depserv.sh
|
||||||
TESTS += devmon.sh
|
TESTS += devmon.sh
|
||||||
TESTS += failing-sysv.sh
|
TESTS += failing-sysv.sh
|
||||||
@@ -118,6 +121,7 @@ if KEVENTD
|
|||||||
TESTS += keventd.sh
|
TESTS += keventd.sh
|
||||||
endif
|
endif
|
||||||
TESTS += notify.sh
|
TESTS += notify.sh
|
||||||
|
TESTS += pam-session.sh
|
||||||
TESTS += pidfile.sh
|
TESTS += pidfile.sh
|
||||||
TESTS += stale-pidfile.sh
|
TESTS += stale-pidfile.sh
|
||||||
TESTS += pre-post-serv.sh
|
TESTS += pre-post-serv.sh
|
||||||
|
|||||||
+11
-1
@@ -39,6 +39,15 @@ BBURL ?= $(BBHOME)/$(BBVER)/$(BBBIN)
|
|||||||
_libs_nss := $(firstword $(wildcard /lib/$(ARCH)-linux-gnu/libnss_files.so.2 \
|
_libs_nss := $(firstword $(wildcard /lib/$(ARCH)-linux-gnu/libnss_files.so.2 \
|
||||||
/usr/lib/$(ARCH)-linux-gnu/libnss_files.so.2 \
|
/usr/lib/$(ARCH)-linux-gnu/libnss_files.so.2 \
|
||||||
/lib64/libnss_files.so.2 /lib/libnss_files.so.2))
|
/lib64/libnss_files.so.2 /lib/libnss_files.so.2))
|
||||||
|
# PAM dlopen()s its modules too, so ldd cannot see them either. Only
|
||||||
|
# the three the pam-session test needs; absent is fine, it skips. Their
|
||||||
|
# own dependencies are picked up by the ldd pass below. libpam looks in
|
||||||
|
# one compiled-in directory, and here /lib and /usr/lib are two real
|
||||||
|
# directories rather than the host's symlink, so /usr/lib comes first.
|
||||||
|
_pam_mods := $(foreach m,pam_permit.so pam_deny.so pam_limits.so, \
|
||||||
|
$(firstword $(wildcard /usr/lib/$(ARCH)-linux-gnu/security/$(m) \
|
||||||
|
/lib/$(ARCH)-linux-gnu/security/$(m) \
|
||||||
|
/lib/security/$(m))))
|
||||||
# A real broker and a real client, staged when the host has them, so
|
# A real broker and a real client, staged when the host has them, so
|
||||||
# one test can check Finit against dbus-daemon instead of only against
|
# one test can check Finit against dbus-daemon instead of only against
|
||||||
# libink's own client. Absent is fine, dbus-broker.sh skips.
|
# libink's own client. Absent is fine, dbus-broker.sh skips.
|
||||||
@@ -53,7 +62,8 @@ _bins := $(FINITBIN) $(dbus_bins) \
|
|||||||
# The dbus binaries stage exactly like the libraries: same host path,
|
# The dbus binaries stage exactly like the libraries: same host path,
|
||||||
# same path under DEST, copied by the rule below.
|
# same path under DEST, copied by the rule below.
|
||||||
_libs_src := $(foreach bin,$(_bins),$(shell ldd $(bin) 2>/dev/null | grep -Eo '/[^ ]+')) \
|
_libs_src := $(foreach bin,$(_bins),$(shell ldd $(bin) 2>/dev/null | grep -Eo '/[^ ]+')) \
|
||||||
$(_libs_nss) $(dbus_bins)
|
$(foreach mod,$(_pam_mods),$(shell ldd $(mod) 2>/dev/null | grep -Eo '/[^ ]+')) \
|
||||||
|
$(_libs_nss) $(_pam_mods) $(dbus_bins)
|
||||||
libs := $(foreach path,$(sort $(_libs_src)),$(abspath $(DEST))$(path))
|
libs := $(foreach path,$(sort $(_libs_src)),$(abspath $(DEST))$(path))
|
||||||
|
|
||||||
all: $(libs) $(DEST)/bin/$(BBBIN)
|
all: $(libs) $(DEST)/bin/$(BBBIN)
|
||||||
|
|||||||
Executable
+122
@@ -0,0 +1,122 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# A service can run inside a PAM session, so the session stack in
|
||||||
|
# /etc/pam.d applies to the process that becomes the daemon.
|
||||||
|
#
|
||||||
|
# The session outlives the setup: a keeper process, (finit-pam), holds
|
||||||
|
# the PAM handle and closes the session when the service dies. Here we
|
||||||
|
# check that it appears and disappears with the service, that a denied
|
||||||
|
# account stack keeps the service from running at all, and that a
|
||||||
|
# missing pam.d config, or a value that is a path rather than a name,
|
||||||
|
# does the same.
|
||||||
|
#
|
||||||
|
# Skipped unless Finit was built --enable-pam and the host had PAM
|
||||||
|
# modules for lib/sysroot.mk to stage.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
TEST_DIR=$(dirname "$0")
|
||||||
|
|
||||||
|
test_teardown()
|
||||||
|
{
|
||||||
|
say "Running test teardown."
|
||||||
|
run "rm -f $FINIT_CONF"
|
||||||
|
run "rm -f /tmp/pre"
|
||||||
|
run "rm -rf /etc/pam.d"
|
||||||
|
run "rm -rf /etc/security"
|
||||||
|
}
|
||||||
|
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. "$TEST_DIR/lib/setup.sh"
|
||||||
|
|
||||||
|
# shellcheck disable=SC2154 # top_builddir comes from AM_TESTS_ENVIRONMENT
|
||||||
|
grep -q "define HAVE_LIBPAM" "$top_builddir/config.h" 2>/dev/null \
|
||||||
|
|| skip "Finit built without --enable-pam"
|
||||||
|
|
||||||
|
# Staged by lib/sysroot.mk from the host, when it has them. All three,
|
||||||
|
# not just pam_permit.so: without pam_deny.so the denied case would fail
|
||||||
|
# for the wrong reason and still look like a pass.
|
||||||
|
# shellcheck disable=SC2016 # the globs are for the shell inside texec
|
||||||
|
PAMDIR=$(texec sh -c 'for d in /lib/*/security /usr/lib/*/security /lib/security; do
|
||||||
|
test -f "$d/pam_permit.so" || continue
|
||||||
|
test -f "$d/pam_deny.so" || continue
|
||||||
|
test -f "$d/pam_limits.so" || continue
|
||||||
|
echo "$d" && break
|
||||||
|
done')
|
||||||
|
[ -n "$PAMDIR" ] || skip "no PAM modules in the test root, need them on the host"
|
||||||
|
|
||||||
|
assert_keeper()
|
||||||
|
{
|
||||||
|
assert "$1 PAM session keeper(s)" \
|
||||||
|
"$(texec sh -c 'cat /proc/[0-9]*/comm 2>/dev/null | grep -c "(finit-pam)"' || true)" -eq "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# pamsess_open() leaves the child with EX_OSERR, so a service that fails
|
||||||
|
# for any other reason -- a bad command, a pidfile it cannot write -- is
|
||||||
|
# a test failure rather than a pass.
|
||||||
|
denied="crashed (code=exited, status=71/OSERR)"
|
||||||
|
|
||||||
|
say 'A permissive PAM config, and one that denies the account stack'
|
||||||
|
run "mkdir -p /etc/pam.d"
|
||||||
|
run "printf 'auth required pam_permit.so\naccount required pam_permit.so\nsession required pam_permit.so\n' > /etc/pam.d/finit-ok"
|
||||||
|
run "printf 'auth required pam_permit.so\naccount required pam_deny.so\nsession required pam_permit.so\n' > /etc/pam.d/finit-no"
|
||||||
|
|
||||||
|
say 'A service in a session runs, and a keeper appears alongside it'
|
||||||
|
run "printf 'service pamok {\n runlevel = \"S12345\"\n user = \"daemon\"\n pam = \"finit-ok\"\n command = \"/sbin/serv -n -i pamok\"\n}\n' > $FINIT_CONF"
|
||||||
|
run "initctl reload"
|
||||||
|
retry 'assert_status pamok running'
|
||||||
|
retry 'assert_keeper 1'
|
||||||
|
|
||||||
|
say 'Stopping the service takes the keeper with it'
|
||||||
|
run "initctl stop pamok"
|
||||||
|
retry 'assert_status pamok stopped'
|
||||||
|
retry 'assert_keeper 0'
|
||||||
|
|
||||||
|
say 'Starting again opens a fresh session'
|
||||||
|
run "initctl start pamok"
|
||||||
|
retry 'assert_status pamok running'
|
||||||
|
retry 'assert_keeper 1'
|
||||||
|
run "initctl stop pamok"
|
||||||
|
retry 'assert_keeper 0'
|
||||||
|
|
||||||
|
say 'A denied account stack keeps the service from running'
|
||||||
|
run "printf 'service pamno {\n runlevel = \"S12345\"\n user = \"daemon\"\n pam = \"finit-no\"\n command = \"/sbin/serv -n -i pamno\"\n}\n' > $FINIT_CONF"
|
||||||
|
run "initctl reload"
|
||||||
|
retry "assert_status_full pamno '$denied'" 500
|
||||||
|
assert_keeper 0
|
||||||
|
|
||||||
|
say 'So does a pam.d config that is not there'
|
||||||
|
run "printf 'service pamgone {\n runlevel = \"S12345\"\n user = \"daemon\"\n pam = \"finit-no-such-config\"\n command = \"/sbin/serv -n -i pamgone\"\n}\n' > $FINIT_CONF"
|
||||||
|
run "initctl reload"
|
||||||
|
retry "assert_status_full pamgone '$denied'" 500
|
||||||
|
assert_keeper 0
|
||||||
|
|
||||||
|
say 'A pam value that is a path is refused, the service does not start'
|
||||||
|
run "printf 'service pampath {\n runlevel = \"S12345\"\n user = \"daemon\"\n pam = \"/etc/pam.d/finit-ok\"\n command = \"/sbin/serv -n -i pampath\"\n}\n' > $FINIT_CONF"
|
||||||
|
run "initctl reload"
|
||||||
|
retry 'assert_status pampath missing'
|
||||||
|
assert_keeper 0
|
||||||
|
|
||||||
|
# The pre: script forks from SVC_SETUP_STATE, before service_start()
|
||||||
|
# runs, so it is pamsess_open() that has to refuse the value here. The
|
||||||
|
# script exits 71 without ever running, hence no /tmp/pre.
|
||||||
|
say 'And a pre: script does not get the refused value either'
|
||||||
|
run "printf 'service pampre {\n runlevel = \"S12345\"\n user = \"daemon\"\n pam = \"/etc/pam.d/finit-ok\"\n exec-start-pre = \"/bin/pre.sh\"\n command = \"/sbin/serv -n -i pampre\"\n}\n' > $FINIT_CONF"
|
||||||
|
run "initctl reload"
|
||||||
|
retry "assert_status_full pampre '$denied'" 500
|
||||||
|
assert "pre: script did not run" "$(texec sh -c 'test -e /tmp/pre && echo yes || echo no')" = "no"
|
||||||
|
assert_keeper 0
|
||||||
|
|
||||||
|
say 'pam_limits wins over the per-service rlimit'
|
||||||
|
run "mkdir -p /etc/security"
|
||||||
|
run "printf 'daemon hard nofile 512\ndaemon soft nofile 512\n' > /etc/security/limits.conf"
|
||||||
|
run "printf 'auth required pam_permit.so\naccount required pam_permit.so\nsession required pam_limits.so\n' > /etc/pam.d/finit-lim"
|
||||||
|
run "printf 'service pamlim {\n runlevel = \"S12345\"\n user = \"daemon\"\n pam = \"finit-lim\"\n rlimit {\n nofile = 4096\n }\n command = \"/sbin/serv -n -i pamlim\"\n}\n' > $FINIT_CONF"
|
||||||
|
run "initctl reload"
|
||||||
|
retry 'assert_status pamlim running'
|
||||||
|
|
||||||
|
pamlim_pid=$(texec initctl -j status pamlim | jq -M .pid)
|
||||||
|
# shellcheck disable=SC2016 # $4 is awk's field reference, not the shell's
|
||||||
|
assert "nofile is 512, from limits.conf, not 4096 from the block" \
|
||||||
|
"$(texec awk '/Max open files/ { print $4 }' "/proc/$pamlim_pid/limits")" = "512"
|
||||||
|
|
||||||
|
run "initctl stop pamlim"
|
||||||
|
retry 'assert_keeper 0'
|
||||||
Executable
+76
@@ -0,0 +1,76 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# A reload must not be lost on a service paused by a condition
|
||||||
|
#
|
||||||
|
# svc_s writes its PID file one second after a SIGHUP, so a service
|
||||||
|
# with <pid/svc_s> is paused for that second whenever svc_s reloads.
|
||||||
|
# svc_x counts the SIGHUPs it gets. Both .conf files are touched and
|
||||||
|
# two reloads are requested back to back, so the second one arrives
|
||||||
|
# while svc_x is paused with its own reload still pending. svc_x must
|
||||||
|
# get exactly one SIGHUP and keep its PID.
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
TEST_DIR=$(dirname "$0")
|
||||||
|
|
||||||
|
test_teardown()
|
||||||
|
{
|
||||||
|
say "Running test teardown."
|
||||||
|
run "rm -f $FINIT_RCSD/svc_s.conf $FINIT_RCSD/svc_x.conf /tmp/hup.sh /tmp/hup.log"
|
||||||
|
}
|
||||||
|
|
||||||
|
pidof()
|
||||||
|
{
|
||||||
|
texec initctl -j status "$1" | jq .pid
|
||||||
|
}
|
||||||
|
|
||||||
|
test_setup()
|
||||||
|
{
|
||||||
|
run "cat > /tmp/hup.sh" <<EOF
|
||||||
|
#!/bin/sh
|
||||||
|
trap 'echo HUP >> /tmp/hup.log' HUP
|
||||||
|
trap 'exit 0' TERM
|
||||||
|
while true; do
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
EOF
|
||||||
|
run "cat > $FINIT_RCSD/svc_s.conf" <<EOF
|
||||||
|
service log:stdout notify:pid pid:!/run/service.pid name:svc_s service.sh -- Slow to reassert
|
||||||
|
EOF
|
||||||
|
run "cat > $FINIT_RCSD/svc_x.conf" <<EOF
|
||||||
|
service log:stdout <pid/svc_s> name:svc_x /bin/sh /tmp/hup.sh -- Paused while svc_s reloads
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. "$TEST_DIR/lib/setup.sh"
|
||||||
|
|
||||||
|
sep "Configuration"
|
||||||
|
run "cat $FINIT_RCSD/svc_s.conf"
|
||||||
|
run "cat $FINIT_RCSD/svc_x.conf"
|
||||||
|
|
||||||
|
say "Reload Finit to start both services"
|
||||||
|
run "initctl reload"
|
||||||
|
retry 'assert_status "svc_x" "running"' 10 1
|
||||||
|
|
||||||
|
pid_x=$(pidof svc_x)
|
||||||
|
say "svc_x PID before: $pid_x"
|
||||||
|
|
||||||
|
sep "Touch both, reload twice"
|
||||||
|
run "rm -f /tmp/hup.log"
|
||||||
|
run "initctl touch svc_s.conf"
|
||||||
|
run "initctl touch svc_x.conf"
|
||||||
|
run "initctl reload"
|
||||||
|
run "initctl reload"
|
||||||
|
|
||||||
|
say "Wait for services to settle"
|
||||||
|
retry 'assert_status "svc_x" "running"' 15 1
|
||||||
|
run "initctl status"
|
||||||
|
|
||||||
|
# shellcheck disable=SC2016
|
||||||
|
retry 'assert "svc_x got its SIGHUP" "$(texec sh -c "cat /tmp/hup.log 2>/dev/null | wc -l")" -eq 1' 10 1
|
||||||
|
|
||||||
|
new_pid_x=$(pidof svc_x)
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
assert "svc_x was not restarted" $new_pid_x -eq $pid_x
|
||||||
|
|
||||||
|
return 0
|
||||||
Reference in New Issue
Block a user