pi-polkit 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +80 -0
- package/README.md +97 -20
- package/package.json +3 -2
- package/polkit-redirect.ts +11 -7
- package/polkit-shims.ts +440 -90
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,86 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.2.1] - 2026-11-07
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `test/polkit-shims.test.ts`: behaviour tests for the generated shims. They
|
|
13
|
+
stub `pkexec`, `pkcheck` and `pkaction` on `PATH`, so they need neither polkit,
|
|
14
|
+
a desktop agent nor root; `npm test` runs them with Node's test runner.
|
|
15
|
+
- README: account-lockout section. Where the `polkit-1` PAM service includes
|
|
16
|
+
`pam_faillock`, a wrong or empty password submitted in the dialog adds a
|
|
17
|
+
record shared with terminal sudo and login; dismissing the dialog adds none,
|
|
18
|
+
an agent may allow several attempts in one dialog (each retry starts a new PAM
|
|
19
|
+
helper), and a locked account rejects even the correct password for
|
|
20
|
+
`unlock_time`. Documents how to inspect and reset the tally, and why retrying
|
|
21
|
+
does not help.
|
|
22
|
+
- README limitation: `-n`, `-v` and `-l` check
|
|
23
|
+
`org.freedesktop.policykit.exec` itself, while pkexec may select another
|
|
24
|
+
action installed with `org.freedesktop.policykit.exec.path`/`argv1`
|
|
25
|
+
annotations.
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- The bash tool's prompt guideline now asks the model to consult the user before
|
|
30
|
+
retrying a dismissed or failed dialog, because failed or empty password
|
|
31
|
+
submissions count toward a system-wide `pam_faillock` lockout while a dismissal
|
|
32
|
+
does not.
|
|
33
|
+
- README: `sudoers` and the `sudo` PAM stack are not consulted; polkit policy and
|
|
34
|
+
the `polkit-1` PAM service decide, so sudo-only modules such as a fingerprint
|
|
35
|
+
reader or a second factor do not run in Pi.
|
|
36
|
+
- README now describes agent caching and retry behaviour generically instead of
|
|
37
|
+
attributing it to one desktop agent.
|
|
38
|
+
- `sudo -v` reports an interactive failure as "polkit did not authorize" instead
|
|
39
|
+
of "polkit denies", since the check can fail after the password is rejected.
|
|
40
|
+
|
|
41
|
+
## [0.2.0] - 2026-11-05
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
|
|
45
|
+
- `sudo -n`, `doas -n`: a non-interactive `pkcheck` on
|
|
46
|
+
`org.freedesktop.policykit.exec` decides whether the command runs. polkit grants
|
|
47
|
+
it without asking, or the shim exits `1` with `sudo: a password is required` and
|
|
48
|
+
the reason that dropping `-n` lets the agent answer. Measured: a successful
|
|
49
|
+
`pkexec` leaves no temporary authorization behind (`pkcheck --list-temp` stays
|
|
50
|
+
empty), so the silent second run is the agent's own password cache and no
|
|
51
|
+
non-interactive query can predict it; `-n` therefore reports policy, not a
|
|
52
|
+
guess.
|
|
53
|
+
- `sudo -v` performs the authentication (`pkcheck --allow-user-interaction`) and
|
|
54
|
+
exits `0`, `1`, `126` when the dialog is dismissed, or `127`; `sudo -nv` uses
|
|
55
|
+
the non-interactive check.
|
|
56
|
+
- `sudo -l` prints `pkaction --verbose` for the action and this session's
|
|
57
|
+
temporary authorizations.
|
|
58
|
+
- `sudo -k`/`-K` revoke polkit's temporary authorizations and say that an agent's
|
|
59
|
+
own password cache cannot be cleared from here.
|
|
60
|
+
- `sudo -H`, `-N`, `-P` are accepted as no-ops: pkexec already sets `HOME`, keeps
|
|
61
|
+
no timestamp and leaves groups alone.
|
|
62
|
+
- `sudo -s COMMAND` and `--shell` run the command through a shell, `sudo -b`
|
|
63
|
+
starts it in the background and returns, and `sudo -E`/
|
|
64
|
+
`--preserve-env[=LIST]` re-export the environment inside the command.
|
|
65
|
+
- `sudo --help` and `sudo -V` are answered by the real binary, so scripts that
|
|
66
|
+
probe sudo keep working.
|
|
67
|
+
- Short options bundle (`sudo -nu bob`), and unknown long options report their own
|
|
68
|
+
name instead of `--`.
|
|
69
|
+
- Registered bash tool: one guideline line tells the model that elevation goes
|
|
70
|
+
through polkit, what `-n`, `-v` and `-l` answer, so it plans with the real
|
|
71
|
+
contract instead of learning it from a failed call.
|
|
72
|
+
- `pi-polkit-env`: helper that turns `NAME=VALUE` arguments into export
|
|
73
|
+
statements. `-E` passes entries through argv (`env -0` into `xargs -0`), so
|
|
74
|
+
spaces, quotes and newlines survive, and it drops every shim directory from
|
|
75
|
+
`PATH` so an elevated command cannot resolve sudo back to a shim.
|
|
76
|
+
|
|
77
|
+
### Changed
|
|
78
|
+
|
|
79
|
+
- The shims share one prologue: `fail`, the polkit subject (pinned with
|
|
80
|
+
`pid,start-time,uid` as `pkcheck(1)` requires), the two checks and the `pkexec`
|
|
81
|
+
wrapper.
|
|
82
|
+
- `-n` failures exit `1` like sudo, not `64`: `64` now means only that the
|
|
83
|
+
invocation cannot be expressed. README documents the four-way handling
|
|
84
|
+
(translated, no-op, checked, refused) and polkit's missing temp authorization.
|
|
85
|
+
- `sudo -i`, `-S`, `-A`, `sudoedit` and `visudo` still refuse, now from the shared
|
|
86
|
+
prelude.
|
|
87
|
+
|
|
8
88
|
## [0.1.1] - 2026-11-04
|
|
9
89
|
|
|
10
90
|
### Added
|
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# pi-polkit
|
|
2
2
|
|
|
3
3
|
Redirects `sudo`, `doas` and `su -c` to `pkexec` inside Pi, so authorisation
|
|
4
|
-
goes through the desktop's polkit agent
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
goes through the desktop's polkit agent instead of a terminal the shell tools do
|
|
5
|
+
not have. Applies to the `bash` tool and to `!` user commands; `sudo` in other
|
|
6
|
+
terminals is unchanged.
|
|
7
7
|
|
|
8
8
|
## Scope
|
|
9
9
|
|
|
@@ -46,8 +46,16 @@ pi -e ./pi-polkit
|
|
|
46
46
|
| `sudo apt install x` | polkit dialog, then runs as root |
|
|
47
47
|
| `sudo -u bob …` | as above, as `bob` |
|
|
48
48
|
| `cat x \| sudo tee y` | dialog for `tee` |
|
|
49
|
-
| `sudo -n …` |
|
|
50
|
-
| `sudo -
|
|
49
|
+
| `sudo -n …` | runs only if polkit would authorise without asking; otherwise exit 1 with `sudo: a password is required` |
|
|
50
|
+
| `sudo -v` | authenticates through polkit; creates no timestamp |
|
|
51
|
+
| `sudo -l` | prints the polkit policy and this session's temporary authorizations |
|
|
52
|
+
| `sudo -k`, `-K` | revokes polkit's temporary authorizations |
|
|
53
|
+
| `sudo -H`, `-N`, `-P` | accepted: pkexec already does what they ask |
|
|
54
|
+
| `sudo -s CMD` | runs `CMD` through a shell, as root |
|
|
55
|
+
| `sudo -E …` | runs with the caller's environment |
|
|
56
|
+
| `sudo -b …` | starts the command in the background |
|
|
57
|
+
| `sudo --help`, `-V` | answered by the real sudo; nothing elevates |
|
|
58
|
+
| `sudo -i`, `-S`, `-A` | refused, with the equivalent command |
|
|
51
59
|
| `sudoedit`, `visudo` | refused, with the alternative |
|
|
52
60
|
| `su -c 'id'` | dialog, then runs as root |
|
|
53
61
|
| `su -c 'id' bob` | as above, as `bob` (administrator password) |
|
|
@@ -55,7 +63,9 @@ pi -e ./pi-polkit
|
|
|
55
63
|
| `su -m`, `-s`, `-g`, `-w`, `--pty` | refused, with the alternative |
|
|
56
64
|
| `pkexec …` | passed through |
|
|
57
65
|
|
|
58
|
-
|
|
66
|
+
An option is refused only when polkit cannot express it: it needs a terminal, a
|
|
67
|
+
password channel polkit does not have, or an editor. Refusals exit with status 64
|
|
68
|
+
and one line naming the alternative.
|
|
59
69
|
|
|
60
70
|
## How it works
|
|
61
71
|
|
|
@@ -73,7 +83,9 @@ Two wiring paths put the stand-ins on `PATH`, one per execution form:
|
|
|
73
83
|
line instead.
|
|
74
84
|
|
|
75
85
|
The shim directory is created lazily in `$XDG_RUNTIME_DIR` (falling back to
|
|
76
|
-
`/tmp`) as `0700` and removed at session shutdown.
|
|
86
|
+
`/tmp`) as `0700` and removed at session shutdown. It also holds
|
|
87
|
+
`pi-polkit-env`, the helper that `sudo -E` uses to re-export an environment
|
|
88
|
+
pkexec stripped.
|
|
77
89
|
|
|
78
90
|
## Authorisation
|
|
79
91
|
|
|
@@ -81,12 +93,66 @@ The shim directory is created lazily in `$XDG_RUNTIME_DIR` (falling back to
|
|
|
81
93
|
one; other actions may not, according to policy. The extension takes no part in
|
|
82
94
|
that decision.
|
|
83
95
|
|
|
96
|
+
`sudoers` is not consulted: polkit policy decides who may run what, so per-user
|
|
97
|
+
and per-command rules, `NOPASSWD`, `secure_path` and `env_keep` do not apply.
|
|
98
|
+
Authentication runs through the `polkit-1` PAM service, not `/etc/pam.d/sudo`,
|
|
99
|
+
so a module configured only for sudo — a fingerprint reader, a second factor —
|
|
100
|
+
is not used here; the service's own stack (normally `system-auth`) decides how
|
|
101
|
+
the password is verified.
|
|
102
|
+
|
|
84
103
|
`org.freedesktop.policykit.exec` asks for administrator authentication on every
|
|
85
|
-
call, but
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
agent
|
|
89
|
-
|
|
104
|
+
call, but an agent may keep the password it just received and answer a later
|
|
105
|
+
challenge itself. In that case a second command can run without a dialog. That
|
|
106
|
+
cache belongs to the agent: a different agent, a restarted agent or a change in
|
|
107
|
+
the agent's state can mean the next command prompts again, so do not rely on it.
|
|
108
|
+
|
|
109
|
+
With the default policy, polkit records no temporary authorization for this
|
|
110
|
+
action: after a successful `pkexec`, `pkcheck --list-temp` stays empty and a
|
|
111
|
+
non-interactive `pkcheck` still reports that authentication is required. Whether
|
|
112
|
+
a later command prompts is therefore the agent's decision, and no user-space
|
|
113
|
+
query can predict it. That is why `sudo -n` reports what the policy says rather
|
|
114
|
+
than guessing whether a dialog would appear, and why `sudo -k` can only clear
|
|
115
|
+
polkit's own record.
|
|
116
|
+
|
|
117
|
+
The extension keeps no authorization state of its own: `sudo -v` verifies once
|
|
118
|
+
and writes nothing, `sudo -n` assumes nothing, and `sudo -k` only clears polkit's
|
|
119
|
+
record. Authorising one command therefore leaves the next one to be decided
|
|
120
|
+
again, which is the point — the person who allowed an upgrade can still refuse
|
|
121
|
+
the deletion that follows. What widens that window is the agent's password
|
|
122
|
+
cache, not this extension, and nothing here can revoke it.
|
|
123
|
+
|
|
124
|
+
## Account lockout
|
|
125
|
+
|
|
126
|
+
When the `polkit-1` PAM service includes `pam_faillock` (Arch's default stack
|
|
127
|
+
does), the dialog shares one failure tally per user with terminal sudo, `su` and
|
|
128
|
+
the login screen:
|
|
129
|
+
|
|
130
|
+
- A wrong or empty password submitted in the dialog adds a record. Dismissing
|
|
131
|
+
the dialog, closing it or pressing Esc cancels the agent's helper before PAM
|
|
132
|
+
returns, and adds none. (An agent process that is killed instead of
|
|
133
|
+
cancelling leaves the helper at EOF, and that attempt can count.)
|
|
134
|
+
- An agent may allow several attempts in one dialog, and each retry starts a
|
|
135
|
+
new PAM helper; a dialog can therefore add several records and use up the
|
|
136
|
+
default `deny=3`.
|
|
137
|
+
- Once a user has `deny` failures within `fail_interval` (900 s by default),
|
|
138
|
+
`pam_faillock` refuses that user in every service using the stack for
|
|
139
|
+
`unlock_time` (600 s by default, measured from the newest record). A locked
|
|
140
|
+
account rejects even the correct password, because `preauth` fails and
|
|
141
|
+
`authsucc` cannot reset the tally; further attempts neither extend nor shorten
|
|
142
|
+
the lock. A successful password authentication outside a lock resets the
|
|
143
|
+
tally.
|
|
144
|
+
- `sudo -k` clears polkit's authorizations, not the tally. The locked user's own
|
|
145
|
+
authentication cannot succeed, so Pi cannot elevate with that user's
|
|
146
|
+
credentials to reset it: wait for the lock to expire, use a root console or
|
|
147
|
+
another administrator, or reboot (`/run` is cleared). Where the tally file is
|
|
148
|
+
readable by the user, `faillock --user "$USER"` shows the records and
|
|
149
|
+
`faillock --user "$USER" --reset` clears them without root; otherwise both
|
|
150
|
+
need root. Check readability with `ls -l /run/faillock/$USER`.
|
|
151
|
+
|
|
152
|
+
Retrying a failed dialog in a loop only gives the user more chances to submit a
|
|
153
|
+
wrong or empty password, and the lock applies to the whole account. The prompt
|
|
154
|
+
guideline therefore tells the model to ask before retrying, and `sudo -n` remains
|
|
155
|
+
the probe that never invokes PAM.
|
|
90
156
|
|
|
91
157
|
## Limitations
|
|
92
158
|
|
|
@@ -96,6 +162,15 @@ again, so do not rely on it.
|
|
|
96
162
|
- `su -c … USER` authenticates an administrator (polkit), not `USER`, so an
|
|
97
163
|
account that real `su` would allow can be refused here.
|
|
98
164
|
- `su -c` runs the command with `sh -c`, not with the target user's login shell.
|
|
165
|
+
- `-n`, `-v` and `-l` check `org.freedesktop.policykit.exec` itself. pkexec may
|
|
166
|
+
select a different action installed with an
|
|
167
|
+
`org.freedesktop.policykit.exec.path` (and `argv1`) annotation, so that
|
|
168
|
+
action's rules are not reflected by these checks.
|
|
169
|
+
- `sudo -E` re-exports the environment through `pkexec`'s argument list, so the
|
|
170
|
+
values are visible in `ps` while the command starts. Exported shell functions
|
|
171
|
+
are skipped, and `PATH` loses the shim directories.
|
|
172
|
+
- `sudo -k` cannot clear the password a desktop agent holds; only polkit's
|
|
173
|
+
temporary authorizations are revoked.
|
|
99
174
|
- PowerShell is not handled; the extension targets Linux only.
|
|
100
175
|
|
|
101
176
|
These bypass the redirect, not polkit, which still decides whether the command
|
|
@@ -108,19 +183,21 @@ without one, `pkexec` fails instead of prompting.
|
|
|
108
183
|
|
|
109
184
|
## Exit status
|
|
110
185
|
|
|
111
|
-
| Status | Meaning
|
|
112
|
-
|
|
113
|
-
|
|
|
114
|
-
|
|
|
115
|
-
|
|
|
186
|
+
| Status | Meaning |
|
|
187
|
+
|--------|--------------------------------------------------------|
|
|
188
|
+
| 1 | `-n` or `-v` could not be satisfied: polkit requires authentication, denies it, or authentication failed |
|
|
189
|
+
| 126 | the polkit dialog was dismissed |
|
|
190
|
+
| 127 | not authorized (including after failed authentication), or the program could not run |
|
|
191
|
+
| 64 | the extension refused the invocation |
|
|
116
192
|
|
|
117
193
|
Messages are in English.
|
|
118
194
|
|
|
119
195
|
## Development
|
|
120
196
|
|
|
121
|
-
`npm install
|
|
122
|
-
`@earendil-works/pi-coding-agent` 1.4.0, the API Pi serves to Node
|
|
123
|
-
|
|
197
|
+
`npm install`, then `npm run check` type-checks against the pinned
|
|
198
|
+
`@earendil-works/pi-coding-agent` 1.4.0, the API Pi serves to Node extensions,
|
|
199
|
+
and `npm test` runs the shim behaviour tests. The tests stub `pkexec`, `pkcheck`
|
|
200
|
+
and `pkaction` on `PATH`, so they need neither polkit, a desktop agent nor root.
|
|
124
201
|
|
|
125
202
|
## License
|
|
126
203
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-polkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Redirect sudo, doas and su to pkexec in Pi via PATH shims, so the desktop polkit agent handles authentication",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"pi": {
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
],
|
|
15
15
|
"scripts": {
|
|
16
16
|
"check": "tsc -p tsconfig.json --noEmit",
|
|
17
|
-
"
|
|
17
|
+
"test": "node --experimental-strip-types --test test/*.test.ts",
|
|
18
|
+
"prepublishOnly": "npm run check && npm test"
|
|
18
19
|
},
|
|
19
20
|
"peerDependencies": {
|
|
20
21
|
"@earendil-works/pi-coding-agent": "*"
|
package/polkit-redirect.ts
CHANGED
|
@@ -4,9 +4,9 @@ import { polkitShimsPathExportLine, removePolkitShims, withPolkitShimsOnPath } f
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Redirects sudo, doas and su -c to pkexec inside Pi, so authorisation goes
|
|
7
|
-
* through the desktop's polkit agent
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* through the desktop's polkit agent instead of a terminal the shell tools do
|
|
8
|
+
* not have. Applies to the `bash` tool and to `!` user commands; `sudo` in other
|
|
9
|
+
* terminals is unchanged.
|
|
10
10
|
*
|
|
11
11
|
* The redirect is a PATH prepend, not a command rewrite: the shims in
|
|
12
12
|
* polkit-shims.ts stand in for the sudo/doas/su binaries, and the shell's own
|
|
@@ -19,11 +19,15 @@ export default function polkitRedirect(pi: ExtensionAPI): void {
|
|
|
19
19
|
// The bash tool spawns with a prepared environment, so the shims reach it as
|
|
20
20
|
// a PATH prepend. Registering the built-in's own definition under its own
|
|
21
21
|
// name replaces it and keeps its prompt metadata and renderers.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
const bash = createBashToolDefinition(process.cwd(), {
|
|
23
|
+
spawnHook: ({ command, cwd, env }) => ({ command, cwd, env: withPolkitShimsOnPath(env) }),
|
|
24
|
+
})
|
|
25
|
+
// The model plans privilege with what it knows about sudo. Say once, in the
|
|
26
|
+
// system prompt, how this shell differs, so it does not learn it by failing.
|
|
27
|
+
bash.promptGuidelines?.push(
|
|
28
|
+
"`sudo`, `doas` and `su -c` are routed to pkexec, so a desktop polkit dialog authorises them instead of a terminal password prompt. `sudo -n` runs the command only if polkit would authorise it without asking, `sudo -v` performs a real authentication, and `sudo -l` prints the polkit policy that decides. Ask the user before retrying a dismissed or failed dialog instead of looping: on systems with pam_faillock, failed or empty password submissions count toward a system-wide lockout shared with login, while dismissing the dialog does not.",
|
|
26
29
|
)
|
|
30
|
+
pi.registerTool(bash)
|
|
27
31
|
|
|
28
32
|
pi.on("user_bash", () => ({ operations: polkitShimsUserBashOperations() }))
|
|
29
33
|
|
package/polkit-shims.ts
CHANGED
|
@@ -10,103 +10,412 @@ import { isAbsolute, join } from "node:path"
|
|
|
10
10
|
*/
|
|
11
11
|
export const SHIM_USAGE_EXIT = 64
|
|
12
12
|
|
|
13
|
+
/** The action pkexec checks; the checks below ask polkit about the same one. */
|
|
14
|
+
const POLKIT_ACTION = "org.freedesktop.policykit.exec"
|
|
15
|
+
|
|
13
16
|
/**
|
|
14
|
-
* The
|
|
15
|
-
*
|
|
16
|
-
* and never parses what pkexec prints: the exit status is the whole result.
|
|
17
|
-
*
|
|
18
|
-
* Only options with an exact pkexec equivalent are translated. Everything
|
|
19
|
-
* else is refused with an actionable message rather than forwarded, because
|
|
20
|
-
* pkexec would reject it with a message that does not say what to do instead.
|
|
17
|
+
* The part every shim starts with: one refusal helper, and a check that the two
|
|
18
|
+
* polkit tools exist at all.
|
|
21
19
|
*/
|
|
22
|
-
const
|
|
23
|
-
# pi-polkit: sudo shim. Routes sudo to pkexec so polkit can authorize it.
|
|
24
|
-
# Generated at extension startup; do not edit.
|
|
25
|
-
|
|
26
|
-
fail() {
|
|
20
|
+
const SHIM_PRELUDE = `fail() {
|
|
27
21
|
printf 'pi-polkit: %s\\n' "$1" >&2
|
|
28
22
|
exit ${SHIM_USAGE_EXIT}
|
|
29
23
|
}
|
|
30
24
|
|
|
31
25
|
command -v pkexec >/dev/null 2>&1 || fail 'pkexec is not installed; install polkit'
|
|
26
|
+
command -v pkcheck >/dev/null 2>&1 || fail 'pkcheck is not installed; install polkit'
|
|
27
|
+
`
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The checks that answer what sudo's timestamp file used to answer, plus the
|
|
31
|
+
* pkexec wrapper. Only exit statuses are read: pkcheck's messages and its
|
|
32
|
+
* `polkit.result` detail are localised and addressed to humans, so parsing them
|
|
33
|
+
* would break on the next locale or polkit release.
|
|
34
|
+
*/
|
|
35
|
+
const SHIM_CHECKS = `# The action pkexec checks: a pkcheck verdict on it describes what the next
|
|
36
|
+
# pkexec will do, which is how the shims answer what sudo's timestamp answered.
|
|
37
|
+
ACTION=${POLKIT_ACTION}
|
|
38
|
+
|
|
39
|
+
# pkcheck(1) warns that a bare pid races with pid reuse, so pin the start time.
|
|
40
|
+
subject() {
|
|
41
|
+
started=$(awk '{ print $22 }' /proc/$$/stat 2>/dev/null)
|
|
42
|
+
if [ -n "$started" ]; then
|
|
43
|
+
printf '%s,%s,%s' "$$" "$started" "$(id -u)"
|
|
44
|
+
else
|
|
45
|
+
printf '%s' "$$"
|
|
46
|
+
fi
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
# 0 grants the action without asking anyone, 2 requires authentication,
|
|
50
|
+
# 1 refuses it. pkcheck's own 126 (usage) and 127 (error) pass through.
|
|
51
|
+
check_without_prompt() {
|
|
52
|
+
pkcheck --action-id "$ACTION" --process "$(subject)" >/dev/null 2>&1
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# The same check with the desktop agent allowed to answer. An agent that still
|
|
56
|
+
# holds the password answers without a dialog, which is why a 0 here also means
|
|
57
|
+
# the next pkexec runs as root without asking.
|
|
58
|
+
check_with_prompt() {
|
|
59
|
+
pkcheck --action-id "$ACTION" --process "$(subject)" --allow-user-interaction >/dev/null 2>&1
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# -n means "run only if no one has to be asked". polkit cannot say whether the
|
|
63
|
+
# agent would answer from its cache, so a 2 is reported as needing a password
|
|
64
|
+
# and the command is not run: that is what -n promises, in both directions.
|
|
65
|
+
require_without_prompt() {
|
|
66
|
+
if [ -n "$noninteractive" ]; then
|
|
67
|
+
check_without_prompt
|
|
68
|
+
status=$?
|
|
69
|
+
case $status in
|
|
70
|
+
0)
|
|
71
|
+
return 0
|
|
72
|
+
;;
|
|
73
|
+
1)
|
|
74
|
+
printf 'pi-polkit: %s: polkit denies %s to this process\\n' "$PROG" "$ACTION" >&2
|
|
75
|
+
exit 1
|
|
76
|
+
;;
|
|
77
|
+
2)
|
|
78
|
+
printf '%s: a password is required (polkit requires authentication for %s; drop -n to let the desktop agent answer, which it may do from its cache)\\n' "$PROG" "$ACTION" >&2
|
|
79
|
+
exit 1
|
|
80
|
+
;;
|
|
81
|
+
*)
|
|
82
|
+
printf 'pi-polkit: %s: polkit check failed (status %s)\\n' "$PROG" "$status" >&2
|
|
83
|
+
exit 127
|
|
84
|
+
;;
|
|
85
|
+
esac
|
|
86
|
+
fi
|
|
87
|
+
return 0
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# pkexec is silent when the dialog is dismissed (status 126), so say what the
|
|
91
|
+
# status means. Every other failure path already prints its own reason.
|
|
92
|
+
run_pkexec() {
|
|
93
|
+
pkexec "$@"
|
|
94
|
+
status=$?
|
|
95
|
+
if [ $status -eq 126 ]; then
|
|
96
|
+
printf 'pi-polkit: %s\\n' 'the polkit authentication dialog was dismissed' >&2
|
|
97
|
+
fi
|
|
98
|
+
exit $status
|
|
99
|
+
}
|
|
100
|
+
`
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The sudo shim. Lives on PATH ahead of the real sudo, so the shell — not this
|
|
104
|
+
* extension — decides that `sudo` means this file. It forwards to pkexec and
|
|
105
|
+
* never parses what pkexec prints: the exit status is the whole result.
|
|
106
|
+
*
|
|
107
|
+
* Options are handled in four ways, and only the last one refuses. Those whose
|
|
108
|
+
* effect pkexec already has (`-H`, `-N`, `-P`) are accepted as no-ops; those
|
|
109
|
+
* polkit answers better than a run (`-n`, `-v`, `-l`, `-k`) become checks; those
|
|
110
|
+
* a wrapper can express (`-s COMMAND`, `-E`, `-b`) are translated; the rest,
|
|
111
|
+
* which need a terminal or a password channel polkit does not have, are refused
|
|
112
|
+
* with one line naming the alternative.
|
|
113
|
+
*/
|
|
114
|
+
const SUDO_SHIM = `#!/bin/sh
|
|
115
|
+
# pi-polkit: sudo shim. Routes sudo to pkexec so polkit can authorize it.
|
|
116
|
+
# Generated at extension startup; do not edit.
|
|
32
117
|
|
|
118
|
+
${SHIM_PRELUDE}
|
|
119
|
+
${SHIM_CHECKS}
|
|
120
|
+
PROG=sudo
|
|
33
121
|
user=
|
|
122
|
+
noninteractive=
|
|
123
|
+
preserve=
|
|
124
|
+
preserve_names=
|
|
125
|
+
background=
|
|
126
|
+
mode=
|
|
127
|
+
|
|
34
128
|
while [ $# -gt 0 ]; do
|
|
35
129
|
case $1 in
|
|
36
130
|
--)
|
|
37
131
|
shift
|
|
38
132
|
break
|
|
39
133
|
;;
|
|
40
|
-
-u)
|
|
41
|
-
[ $# -ge 2 ] || fail 'sudo -u needs a username: -u USER PROGRAM'
|
|
42
|
-
user=$2
|
|
43
|
-
shift 2
|
|
44
|
-
;;
|
|
45
134
|
--user)
|
|
46
135
|
[ $# -ge 2 ] || fail 'sudo --user needs a username: --user USER PROGRAM'
|
|
47
136
|
user=$2
|
|
48
137
|
shift 2
|
|
49
138
|
;;
|
|
50
|
-
-u?*)
|
|
51
|
-
user=\${1#-u}
|
|
52
|
-
[ -n "$user" ] || fail 'sudo -u needs a username: -u USER PROGRAM'
|
|
53
|
-
shift
|
|
54
|
-
;;
|
|
55
139
|
--user=*)
|
|
56
140
|
user=\${1#--user=}
|
|
57
|
-
[ -n "$user" ] || fail 'sudo --user needs a username: --user USER PROGRAM'
|
|
58
141
|
shift
|
|
59
142
|
;;
|
|
60
|
-
|
|
61
|
-
|
|
143
|
+
--non-interactive)
|
|
144
|
+
noninteractive=1
|
|
145
|
+
shift
|
|
146
|
+
;;
|
|
147
|
+
--preserve-env)
|
|
148
|
+
preserve=1
|
|
149
|
+
shift
|
|
150
|
+
;;
|
|
151
|
+
--preserve-env=*)
|
|
152
|
+
preserve=1
|
|
153
|
+
preserve_names=\${1#--preserve-env=}
|
|
154
|
+
shift
|
|
62
155
|
;;
|
|
63
|
-
|
|
64
|
-
|
|
156
|
+
--set-home | --no-update | --preserve-groups)
|
|
157
|
+
shift
|
|
65
158
|
;;
|
|
66
|
-
|
|
67
|
-
|
|
159
|
+
--list)
|
|
160
|
+
mode=list
|
|
161
|
+
shift
|
|
68
162
|
;;
|
|
69
|
-
|
|
70
|
-
|
|
163
|
+
--validate)
|
|
164
|
+
mode=validate
|
|
165
|
+
shift
|
|
71
166
|
;;
|
|
72
|
-
-
|
|
73
|
-
|
|
167
|
+
--reset-timestamp | --remove-timestamp)
|
|
168
|
+
mode=revoke
|
|
169
|
+
shift
|
|
74
170
|
;;
|
|
75
|
-
|
|
76
|
-
|
|
171
|
+
--background)
|
|
172
|
+
background=1
|
|
173
|
+
shift
|
|
77
174
|
;;
|
|
78
|
-
|
|
79
|
-
|
|
175
|
+
--help)
|
|
176
|
+
mode=help
|
|
177
|
+
shift
|
|
80
178
|
;;
|
|
81
|
-
|
|
82
|
-
|
|
179
|
+
--version)
|
|
180
|
+
mode=version
|
|
181
|
+
shift
|
|
83
182
|
;;
|
|
84
|
-
|
|
85
|
-
|
|
183
|
+
--stdin | --askpass | --edit | --login)
|
|
184
|
+
case $1 in
|
|
185
|
+
--edit)
|
|
186
|
+
fail 'sudoedit is not supported; write a temp file and run pkexec install or pkexec cp on it.'
|
|
187
|
+
;;
|
|
188
|
+
--stdin)
|
|
189
|
+
fail 'sudo -S reads the password from stdin; pkexec authenticates through the polkit agent. Drop -S.'
|
|
190
|
+
;;
|
|
191
|
+
--askpass)
|
|
192
|
+
fail 'SUDO_ASKPASS does not apply to pkexec; drop -A.'
|
|
193
|
+
;;
|
|
194
|
+
*)
|
|
195
|
+
fail 'sudo -i opens a login shell; run pkexec bash instead.'
|
|
196
|
+
;;
|
|
197
|
+
esac
|
|
198
|
+
;;
|
|
199
|
+
--shell)
|
|
200
|
+
mode=shell
|
|
201
|
+
shift
|
|
86
202
|
;;
|
|
87
|
-
|
|
203
|
+
--*)
|
|
88
204
|
fail "option has no pkexec equivalent: $1 (pkexec accepts --user, --keep-cwd, --disable-internal-agent)"
|
|
89
205
|
;;
|
|
206
|
+
-?*)
|
|
207
|
+
rest=\${1#-}
|
|
208
|
+
shift
|
|
209
|
+
while [ -n "$rest" ]; do
|
|
210
|
+
letter=\${rest%"\${rest#?}"}
|
|
211
|
+
rest=\${rest#?}
|
|
212
|
+
case $letter in
|
|
213
|
+
u)
|
|
214
|
+
if [ -n "$rest" ]; then
|
|
215
|
+
user=$rest
|
|
216
|
+
rest=
|
|
217
|
+
else
|
|
218
|
+
[ $# -ge 1 ] || fail 'sudo -u needs a username: -u USER PROGRAM'
|
|
219
|
+
user=$1
|
|
220
|
+
shift
|
|
221
|
+
fi
|
|
222
|
+
;;
|
|
223
|
+
n)
|
|
224
|
+
noninteractive=1
|
|
225
|
+
;;
|
|
226
|
+
E)
|
|
227
|
+
preserve=1
|
|
228
|
+
;;
|
|
229
|
+
H | N | P)
|
|
230
|
+
# pkexec already sets HOME, keeps no timestamp and leaves groups alone.
|
|
231
|
+
;;
|
|
232
|
+
b)
|
|
233
|
+
background=1
|
|
234
|
+
;;
|
|
235
|
+
l)
|
|
236
|
+
mode=list
|
|
237
|
+
;;
|
|
238
|
+
v)
|
|
239
|
+
mode=validate
|
|
240
|
+
;;
|
|
241
|
+
k | K)
|
|
242
|
+
mode=revoke
|
|
243
|
+
;;
|
|
244
|
+
s)
|
|
245
|
+
mode=shell
|
|
246
|
+
;;
|
|
247
|
+
h)
|
|
248
|
+
mode=help
|
|
249
|
+
;;
|
|
250
|
+
V)
|
|
251
|
+
mode=version
|
|
252
|
+
;;
|
|
253
|
+
i)
|
|
254
|
+
fail 'sudo -i opens a login shell; run pkexec bash instead.'
|
|
255
|
+
;;
|
|
256
|
+
e)
|
|
257
|
+
fail 'sudoedit is not supported; write a temp file and run pkexec install or pkexec cp on it.'
|
|
258
|
+
;;
|
|
259
|
+
S)
|
|
260
|
+
fail 'sudo -S reads the password from stdin; pkexec authenticates through the polkit agent. Drop -S.'
|
|
261
|
+
;;
|
|
262
|
+
A)
|
|
263
|
+
fail 'SUDO_ASKPASS does not apply to pkexec; drop -A.'
|
|
264
|
+
;;
|
|
265
|
+
*)
|
|
266
|
+
fail "option has no pkexec equivalent: -$letter (pkexec accepts --user, --keep-cwd, --disable-internal-agent)"
|
|
267
|
+
;;
|
|
268
|
+
esac
|
|
269
|
+
done
|
|
270
|
+
;;
|
|
90
271
|
*)
|
|
91
272
|
break
|
|
92
273
|
;;
|
|
93
274
|
esac
|
|
94
275
|
done
|
|
95
276
|
|
|
96
|
-
#
|
|
97
|
-
#
|
|
98
|
-
|
|
99
|
-
|
|
277
|
+
# --help and --version describe the binary, not the authorisation, so nothing is
|
|
278
|
+
# elevated: answering them exactly keeps scripts that probe sudo working.
|
|
279
|
+
if [ "$mode" = help ] || [ "$mode" = version ]; then
|
|
280
|
+
for candidate in /usr/bin/sudo /bin/sudo /usr/local/bin/sudo; do
|
|
281
|
+
[ -x "$candidate" ] || continue
|
|
282
|
+
if [ "$mode" = help ]; then
|
|
283
|
+
exec "$candidate" --help
|
|
284
|
+
fi
|
|
285
|
+
exec "$candidate" --version
|
|
286
|
+
done
|
|
287
|
+
fail 'the real sudo is not installed, so --help and --version cannot be answered'
|
|
288
|
+
fi
|
|
289
|
+
|
|
290
|
+
# -l reports the policy polkit will apply and the authorizations this session
|
|
291
|
+
# already holds. Both halves are local and never prompt.
|
|
292
|
+
if [ "$mode" = list ]; then
|
|
293
|
+
pkaction --action-id "$ACTION" --verbose || exit 127
|
|
294
|
+
printf '\\npi-polkit: temporary authorizations in this session\\n'
|
|
295
|
+
pkcheck --list-temp || exit 127
|
|
296
|
+
exit 0
|
|
297
|
+
fi
|
|
298
|
+
|
|
299
|
+
# -v asks the same question sudo -v asks: can authentication succeed right now?
|
|
300
|
+
if [ "$mode" = validate ]; then
|
|
301
|
+
if [ -n "$noninteractive" ]; then
|
|
302
|
+
check_without_prompt
|
|
303
|
+
else
|
|
304
|
+
check_with_prompt
|
|
305
|
+
fi
|
|
100
306
|
status=$?
|
|
101
|
-
|
|
102
|
-
|
|
307
|
+
case $status in
|
|
308
|
+
0)
|
|
309
|
+
exit 0
|
|
310
|
+
;;
|
|
311
|
+
1)
|
|
312
|
+
printf 'pi-polkit: sudo: polkit did not authorize %s (the policy denies it, or authentication failed)\\n' "$ACTION" >&2
|
|
313
|
+
exit 1
|
|
314
|
+
;;
|
|
315
|
+
2)
|
|
316
|
+
if [ -n "$noninteractive" ]; then
|
|
317
|
+
printf 'sudo: a password is required (polkit requires authentication for %s)\\n' "$ACTION" >&2
|
|
318
|
+
else
|
|
319
|
+
printf 'pi-polkit: sudo: no polkit authentication agent answered\\n' >&2
|
|
320
|
+
fi
|
|
321
|
+
exit 1
|
|
322
|
+
;;
|
|
323
|
+
3)
|
|
324
|
+
printf 'pi-polkit: %s\\n' 'the polkit authentication dialog was dismissed' >&2
|
|
325
|
+
exit 126
|
|
326
|
+
;;
|
|
327
|
+
*)
|
|
328
|
+
printf 'pi-polkit: sudo: polkit check failed (status %s)\\n' "$status" >&2
|
|
329
|
+
exit 127
|
|
330
|
+
;;
|
|
331
|
+
esac
|
|
332
|
+
fi
|
|
333
|
+
|
|
334
|
+
# -k drops what polkit itself recorded. The password a desktop agent keeps is
|
|
335
|
+
# the agent's, not polkit's, so say that it stays.
|
|
336
|
+
if [ "$mode" = revoke ]; then
|
|
337
|
+
pkcheck --revoke-temp || exit 127
|
|
338
|
+
if [ $# -eq 0 ]; then
|
|
339
|
+
printf 'pi-polkit: %s\\n' 'revoked the temporary polkit authorizations; a desktop agent that caches the password keeps its own copy, so the next command may still not ask' >&2
|
|
340
|
+
exit 0
|
|
103
341
|
fi
|
|
104
|
-
|
|
342
|
+
fi
|
|
343
|
+
|
|
344
|
+
# -E: pkexec strips the environment, so name the variables again inside the shell
|
|
345
|
+
# that runs the command. Entries reach the helper as arguments, so spaces, quotes
|
|
346
|
+
# and newlines survive exactly; the helper also drops every shim directory from
|
|
347
|
+
# PATH, so an elevated command cannot resolve sudo back to a shim.
|
|
348
|
+
shim_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd 2>/dev/null)
|
|
349
|
+
env_helper=$shim_dir/pi-polkit-env
|
|
350
|
+
[ -x "$env_helper" ] || fail "the pi-polkit environment helper is missing: $env_helper"
|
|
351
|
+
|
|
352
|
+
# Runs in the caller's shell, never in a command substitution: an exit there
|
|
353
|
+
# would only end the subshell, and a name reaches eval below, so it must be a
|
|
354
|
+
# name before that happens.
|
|
355
|
+
check_preserve() {
|
|
356
|
+
if [ -z "$preserve_names" ]; then
|
|
357
|
+
command -v xargs >/dev/null 2>&1 || fail '-E needs xargs to forward the environment; name the variables instead: --preserve-env=NAME[,NAME...]'
|
|
358
|
+
return 0
|
|
359
|
+
fi
|
|
360
|
+
old_ifs=$IFS
|
|
361
|
+
IFS=,
|
|
362
|
+
for name in $preserve_names; do
|
|
363
|
+
case $name in
|
|
364
|
+
'' | [0-9]* | *[!A-Za-z0-9_]*)
|
|
365
|
+
IFS=$old_ifs
|
|
366
|
+
fail "not a variable name: $name"
|
|
367
|
+
;;
|
|
368
|
+
esac
|
|
369
|
+
done
|
|
370
|
+
IFS=$old_ifs
|
|
105
371
|
}
|
|
106
372
|
|
|
373
|
+
export_prefix() {
|
|
374
|
+
if [ -n "$preserve_names" ]; then
|
|
375
|
+
set --
|
|
376
|
+
old_ifs=$IFS
|
|
377
|
+
IFS=,
|
|
378
|
+
for name in $preserve_names; do
|
|
379
|
+
is_set=
|
|
380
|
+
eval "is_set=\\\${$name+yes}"
|
|
381
|
+
[ -n "$is_set" ] || continue
|
|
382
|
+
eval "value=\\\${$name}"
|
|
383
|
+
set -- "$@" "$name=$value"
|
|
384
|
+
done
|
|
385
|
+
IFS=$old_ifs
|
|
386
|
+
"$env_helper" "$@"
|
|
387
|
+
return
|
|
388
|
+
fi
|
|
389
|
+
env -0 | xargs -0 -n 64 "$env_helper"
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
require_without_prompt
|
|
393
|
+
|
|
394
|
+
[ $# -gt 0 ] || fail 'sudo needs a command: sudo [OPTIONS] COMMAND (an interactive shell needs a terminal)'
|
|
395
|
+
|
|
396
|
+
# -s runs the command through a shell, which pkexec expresses exactly.
|
|
397
|
+
if [ "$mode" = shell ]; then
|
|
398
|
+
set -- /bin/sh -c "$*"
|
|
399
|
+
fi
|
|
400
|
+
|
|
401
|
+
# The preserved environment is exported by a shell prologue, so the command keeps
|
|
402
|
+
# its own argv rather than being flattened into one string.
|
|
403
|
+
if [ -n "$preserve" ]; then
|
|
404
|
+
check_preserve
|
|
405
|
+
prefix=$(export_prefix)
|
|
406
|
+
set -- /bin/sh -c "\${prefix}exec \\"\\$@\\"" sh "$@"
|
|
407
|
+
fi
|
|
408
|
+
|
|
107
409
|
if [ -n "$user" ]; then
|
|
108
|
-
|
|
410
|
+
set -- --user "$user" "$@"
|
|
109
411
|
fi
|
|
412
|
+
|
|
413
|
+
# -b returns as soon as the command is started, as sudo -b does.
|
|
414
|
+
if [ -n "$background" ]; then
|
|
415
|
+
pkexec "$@" &
|
|
416
|
+
exit 0
|
|
417
|
+
fi
|
|
418
|
+
|
|
110
419
|
run_pkexec "$@"
|
|
111
420
|
`
|
|
112
421
|
|
|
@@ -115,14 +424,13 @@ const DOAS_SHIM = `#!/bin/sh
|
|
|
115
424
|
# pi-polkit: doas shim. Routes doas to pkexec so polkit can authorize it.
|
|
116
425
|
# Generated at extension startup; do not edit.
|
|
117
426
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
command -v pkexec >/dev/null 2>&1 || fail 'pkexec is not installed; install polkit'
|
|
124
|
-
|
|
427
|
+
${SHIM_PRELUDE}
|
|
428
|
+
${SHIM_CHECKS}
|
|
429
|
+
PROG=doas
|
|
125
430
|
user=
|
|
431
|
+
noninteractive=
|
|
432
|
+
mode=
|
|
433
|
+
|
|
126
434
|
while [ $# -gt 0 ]; do
|
|
127
435
|
case $1 in
|
|
128
436
|
--)
|
|
@@ -136,14 +444,15 @@ while [ $# -gt 0 ]; do
|
|
|
136
444
|
;;
|
|
137
445
|
-u?*)
|
|
138
446
|
user=\${1#-u}
|
|
139
|
-
[ -n "$user" ] || fail 'doas -u needs a username: -u USER PROGRAM'
|
|
140
447
|
shift
|
|
141
448
|
;;
|
|
142
449
|
-n)
|
|
143
|
-
|
|
450
|
+
noninteractive=1
|
|
451
|
+
shift
|
|
144
452
|
;;
|
|
145
453
|
-s)
|
|
146
|
-
|
|
454
|
+
mode=shell
|
|
455
|
+
shift
|
|
147
456
|
;;
|
|
148
457
|
-C | -a | -L)
|
|
149
458
|
fail "option has no pkexec equivalent: $1"
|
|
@@ -157,20 +466,18 @@ while [ $# -gt 0 ]; do
|
|
|
157
466
|
esac
|
|
158
467
|
done
|
|
159
468
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
fi
|
|
168
|
-
exit $status
|
|
169
|
-
}
|
|
469
|
+
require_without_prompt
|
|
470
|
+
|
|
471
|
+
[ $# -gt 0 ] || fail 'doas needs a command: doas [OPTIONS] COMMAND (an interactive shell needs a terminal)'
|
|
472
|
+
|
|
473
|
+
if [ "$mode" = shell ]; then
|
|
474
|
+
set -- /bin/sh -c "$*"
|
|
475
|
+
fi
|
|
170
476
|
|
|
171
477
|
if [ -n "$user" ]; then
|
|
172
|
-
|
|
478
|
+
set -- --user "$user" "$@"
|
|
173
479
|
fi
|
|
480
|
+
|
|
174
481
|
run_pkexec "$@"
|
|
175
482
|
`
|
|
176
483
|
|
|
@@ -184,13 +491,9 @@ const SU_SHIM = `#!/bin/sh
|
|
|
184
491
|
# pi-polkit: su shim. Routes su -c to pkexec so polkit can authorize it.
|
|
185
492
|
# Generated at extension startup; do not edit.
|
|
186
493
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
command -v pkexec >/dev/null 2>&1 || fail 'pkexec is not installed; install polkit'
|
|
193
|
-
|
|
494
|
+
${SHIM_PRELUDE}
|
|
495
|
+
${SHIM_CHECKS}
|
|
496
|
+
PROG=su
|
|
194
497
|
user=
|
|
195
498
|
cmd=
|
|
196
499
|
has_cmd=
|
|
@@ -271,26 +574,72 @@ done
|
|
|
271
574
|
[ -n "$has_cmd" ] || fail 'su without -c opens an interactive shell; run pkexec bash instead.'
|
|
272
575
|
|
|
273
576
|
# su keeps the working directory unless it is a login shell, and --keep-cwd is
|
|
274
|
-
# how pkexec does the same.
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
577
|
+
# how pkexec does the same.
|
|
578
|
+
run_pkexec --keep-cwd --user "$user" sh -c "$cmd"
|
|
579
|
+
`
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* The environment helper: turns `NAME=VALUE` arguments into an export statement
|
|
583
|
+
* each, so `-E` can rebuild the environment pkexec stripped. Values arrive as
|
|
584
|
+
* arguments rather than text, so spaces, quotes and newlines need no escaping on
|
|
585
|
+
* the way in. PATH keeps every entry except the shim directories.
|
|
586
|
+
*/
|
|
587
|
+
const POLKIT_ENV_HELPER = `#!/bin/sh
|
|
588
|
+
# pi-polkit: rebuilds a stripped environment. Generated at extension startup;
|
|
589
|
+
# do not edit.
|
|
590
|
+
|
|
591
|
+
for entry in "$@"; do
|
|
592
|
+
name=\${entry%%=*}
|
|
593
|
+
value=\${entry#*=}
|
|
594
|
+
case $name in
|
|
595
|
+
'' | [0-9]* | *[!A-Za-z0-9_]*)
|
|
596
|
+
continue
|
|
597
|
+
;;
|
|
598
|
+
PATH)
|
|
599
|
+
filtered=
|
|
600
|
+
old_ifs=$IFS
|
|
601
|
+
IFS=:
|
|
602
|
+
for dir in $value; do
|
|
603
|
+
case \${dir##*/} in
|
|
604
|
+
pi-polkit-*)
|
|
605
|
+
continue
|
|
606
|
+
;;
|
|
607
|
+
esac
|
|
608
|
+
filtered=$filtered\${filtered:+:}$dir
|
|
609
|
+
done
|
|
610
|
+
IFS=$old_ifs
|
|
611
|
+
value=$filtered
|
|
612
|
+
;;
|
|
613
|
+
esac
|
|
614
|
+
quote="'"
|
|
615
|
+
quoted=
|
|
616
|
+
while :; do
|
|
617
|
+
case $value in
|
|
618
|
+
*"$quote"*)
|
|
619
|
+
quoted=$quoted\${value%%"$quote"*}"'\\\\''"
|
|
620
|
+
value=\${value#*"$quote"}
|
|
621
|
+
;;
|
|
622
|
+
*)
|
|
623
|
+
quoted=$quoted$value
|
|
624
|
+
break
|
|
625
|
+
;;
|
|
626
|
+
esac
|
|
627
|
+
done
|
|
628
|
+
printf "export '%s=%s'; " "$name" "$quoted"
|
|
629
|
+
done
|
|
282
630
|
`
|
|
283
631
|
|
|
632
|
+
|
|
284
633
|
/** sudoedit edits through sudo's own temporary-file flow; polkit has none. */
|
|
285
634
|
const SUDOEDIT_SHIM = `#!/bin/sh
|
|
286
|
-
|
|
287
|
-
|
|
635
|
+
${SHIM_PRELUDE}
|
|
636
|
+
fail 'sudoedit is not supported; write a temp file and run pkexec install or pkexec cp on it.'
|
|
288
637
|
`
|
|
289
638
|
|
|
290
639
|
/** visudo needs an interactive editor on a terminal the shell tool does not have. */
|
|
291
640
|
const VISUDO_SHIM = `#!/bin/sh
|
|
292
|
-
|
|
293
|
-
|
|
641
|
+
${SHIM_PRELUDE}
|
|
642
|
+
fail 'visudo needs an interactive terminal; validate a candidate file with pkexec visudo -c -f FILE instead.'
|
|
294
643
|
`
|
|
295
644
|
|
|
296
645
|
/** File name → script body. Names must match the commands being redirected. */
|
|
@@ -300,6 +649,7 @@ const POLKIT_SHIM_SCRIPTS: Record<string, string> = {
|
|
|
300
649
|
su: SU_SHIM,
|
|
301
650
|
sudoedit: SUDOEDIT_SHIM,
|
|
302
651
|
visudo: VISUDO_SHIM,
|
|
652
|
+
"pi-polkit-env": POLKIT_ENV_HELPER,
|
|
303
653
|
}
|
|
304
654
|
|
|
305
655
|
/** Shim directory of the running extension; created on first use, cleared by removePolkitShims. */
|