Ctrl+Alt+Delete opened Plasma's session screen and a teacher hibernated a classroom computer from it. A hibernated room cannot be recovered by anybody standing in front of it, which on a Sunday morning is everybody. logout=false was already set and did not stop it. That restriction governs Plasma's own actions — what plasmashell draws in a menu or an applet. The chord is a KGlobalAccel shortcut registered by ksmserver and it asks D-Bus for the greeter without consulting a Plasma action at all. A restriction on a menu cannot see a key. Three layers, because each one alone has a way past it. The key. kglobalshortcutsrc unbinds every ksmserver session action and PowerDevil's Sleep, Hibernate, PowerDown and PowerOff, each entry marked immutable. It is deliberately not in the xdg directory with the other settings: kglobalacceld opens that file as a SimpleConfig, so it reads one file in the teacher's home and no system directory at all. Measured — with it in the cascade, kreadconfig6 reported the chord unbound while the shortcut server went on answering Ctrl+Alt+Del. classroom-desktop-reset copies the package's copy into place before the shortcut server starts, the same way the panel is copied out of the package at every login. The policy. A polkit rule refuses login1.suspend* and .hibernate* for the classroom user and for nobody else. This is the layer that matters: a key can be unbound and anything reaching logind can still ask. Refusing here rather than masking sleep.target keeps a technician's own account working, and is also what tells Plasma to stop drawing the button — CanSuspend and CanHibernate come back no. Hybrid sleep and suspend-then-hibernate have no action of their own in systemd 257; both are authorised through hibernate, so denying it denies all three. The greeter. Its D-Bus activation is shadowed with /bin/false, the same mechanism already used for the old notification daemon. Layer one removes the caller, this removes the callee. Measured in the lab, from the package, after a reboot: the escape reproduced first with the shadow removed — the greeter really does start — and then refused, exited 1, with it back. The chord reports nothing bound. systemctl suspend as the classroom user is denied. The maintenance chord and the display chord both still answer, by key code, after the shortcut file is replaced wholesale. A root process may still suspend, hibernate, reboot and power off. 182 checks pass across a logout and back. Screen locking stays disabled, the power button still does nothing, and reboot and shut down are not newly denied to anybody. Co-Authored-By: Claude Opus 5 <[email protected]>
60 lines
2.6 KiB
Plaintext
60 lines
2.6 KiB
Plaintext
// The classroom user cannot put the room to sleep.
|
|
//
|
|
// The second layer under the shortcut that was removed from
|
|
// kglobalshortcutsrc, and the one that matters: a key can be unbound, but
|
|
// anything that can reach logind can still ask for a suspend. PowerDevil
|
|
// does, the logout greeter does, `systemctl suspend` from the maintenance
|
|
// terminal does, and a future Plasma applet would too. They all go through
|
|
// the same polkit actions, so this is the one place that answers all of
|
|
// them at once.
|
|
//
|
|
// Answering here rather than masking sleep.target is deliberate. Masking
|
|
// is machine-wide and would take suspend away from a technician's own
|
|
// account as well; worse, it would be invisible to Plasma, which would go
|
|
// on offering a Hibernate button that silently failed. logind is asked
|
|
// whether this *user* may sleep, so a NO here is both enforcement and the
|
|
// supported way to tell the desktop not to offer it: CanSuspend() and
|
|
// CanHibernate() come back "no" and Plasma stops drawing the buttons.
|
|
//
|
|
// Scoped to the classroom user by name. Every other account — a
|
|
// technician's, root's — is left entirely alone, and nothing here touches
|
|
// power-off or reboot for anybody. A tech standing at the machine, or on
|
|
// SSH, can still shut it down and start it up.
|
|
//
|
|
// To lift this for maintenance, drop a file of the same name into
|
|
// /etc/polkit-1/rules.d/, which polkit reads ahead of this one.
|
|
|
|
polkit.addRule(function (action, subject) {
|
|
if (subject.user !== "classroom") {
|
|
return polkit.Result.NOT_HANDLED;
|
|
}
|
|
|
|
// Every sleep state logind exposes, present and future. systemd maps
|
|
// HybridSleep() and SuspendThenHibernate() onto the hibernate
|
|
// actions today rather than giving them action ids of their own; the
|
|
// two spellings are matched anyway so that a systemd release which
|
|
// splits them out does not quietly reopen this.
|
|
//
|
|
// The -multiple-sessions and -ignore-inhibit variants are the same
|
|
// operation asked for in different circumstances, so the prefixes are
|
|
// matched rather than the exact ids.
|
|
var sleeping = [
|
|
"org.freedesktop.login1.suspend",
|
|
"org.freedesktop.login1.hibernate",
|
|
"org.freedesktop.login1.hybrid-sleep",
|
|
"org.freedesktop.login1.suspend-then-hibernate",
|
|
];
|
|
|
|
for (var i = 0; i < sleeping.length; i++) {
|
|
if (action.id.indexOf(sleeping[i]) === 0) {
|
|
polkit.log(
|
|
"ekklesia-classroom: refusing " + action.id +
|
|
" for the classroom user"
|
|
);
|
|
return polkit.Result.NO;
|
|
}
|
|
}
|
|
|
|
return polkit.Result.NOT_HANDLED;
|
|
});
|