opencode-polkit 0.3.1 → 0.4.0
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 +47 -0
- package/README.md +42 -8
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -1
- package/dist/shims.d.ts +3 -1
- package/dist/shims.js +439 -92
- package/dist/shims.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,53 @@ 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.4.0] - 2026-11-05
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `sudo -n`, `doas -n`: a non-interactive `pkcheck` on
|
|
13
|
+
`org.freedesktop.policykit.exec` decides whether the command runs. polkit grants
|
|
14
|
+
it without asking, or the shim exits `1` with `sudo: a password is required` and
|
|
15
|
+
the reason that dropping `-n` lets the agent answer. Measured: a successful
|
|
16
|
+
`pkexec` leaves no temporary authorization behind (`pkcheck --list-temp` stays
|
|
17
|
+
empty), so the silent second run is the agent's own password cache and no
|
|
18
|
+
non-interactive query can predict it; `-n` therefore reports policy, not a
|
|
19
|
+
guess.
|
|
20
|
+
- `sudo -v` performs the authentication (`pkcheck --allow-user-interaction`) and
|
|
21
|
+
exits `0`, `1`, `126` when the dialog is dismissed, or `127`; `sudo -nv` uses
|
|
22
|
+
the non-interactive check.
|
|
23
|
+
- `sudo -l` prints `pkaction --verbose` for the action and this session's
|
|
24
|
+
temporary authorizations.
|
|
25
|
+
- `sudo -k`/`-K` revoke polkit's temporary authorizations and say that an agent's
|
|
26
|
+
own password cache cannot be cleared from here.
|
|
27
|
+
- `sudo -H`, `-N`, `-P` are accepted as no-ops: pkexec already sets `HOME`, keeps
|
|
28
|
+
no timestamp and leaves groups alone.
|
|
29
|
+
- `sudo -s COMMAND` and `--shell` run the command through a shell, `sudo -b`
|
|
30
|
+
starts it in the background and returns, and `sudo -E`/
|
|
31
|
+
`--preserve-env[=LIST]` re-export the environment inside the command.
|
|
32
|
+
- `sudo --help` and `sudo -V` are answered by the real binary, so scripts that
|
|
33
|
+
probe sudo keep working.
|
|
34
|
+
- Short options bundle (`sudo -nu bob`), and unknown long options report their own
|
|
35
|
+
name instead of `--`.
|
|
36
|
+
- `opencode-polkit-env`: helper that turns `NAME=VALUE` arguments into export
|
|
37
|
+
statements. `-E` passes entries through argv (`env -0` into `xargs -0`), so
|
|
38
|
+
spaces, quotes and newlines survive, and it drops every shim directory from
|
|
39
|
+
`PATH` so an elevated command cannot resolve sudo back to a shim.
|
|
40
|
+
- `src/index.ts`: the shell tool's description gains one sentence naming the
|
|
41
|
+
redirect and what `-n`, `-v` and `-l` answer, so the model plans privilege with
|
|
42
|
+
the real contract instead of learning it from a failed call.
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- The shims share one prologue: `fail`, the polkit subject (pinned with
|
|
47
|
+
`pid,start-time,uid` as `pkcheck(1)` requires), the two checks and the `pkexec`
|
|
48
|
+
wrapper.
|
|
49
|
+
- `-n` failures exit `1` like sudo, not `64`: `64` now means only that the
|
|
50
|
+
invocation cannot be expressed. README documents the four-way handling
|
|
51
|
+
(translated, no-op, checked, refused) and polkit's missing temp authorization.
|
|
52
|
+
- `sudo -i`, `-S`, `-A`, `sudoedit` and `visudo` still refuse, now from the shared
|
|
53
|
+
prelude.
|
|
54
|
+
|
|
8
55
|
## [0.3.1] - 2026-11-04
|
|
9
56
|
|
|
10
57
|
### Added
|
package/README.md
CHANGED
|
@@ -48,8 +48,16 @@ For a local checkout, run `npm run build` once, then reference the directory:
|
|
|
48
48
|
| `sudo apt install x` | polkit dialog, then runs as root |
|
|
49
49
|
| `sudo -u bob …` | as above, as `bob` |
|
|
50
50
|
| `cat x \| sudo tee y` | dialog for `tee` |
|
|
51
|
-
| `sudo -n …` |
|
|
52
|
-
| `sudo -
|
|
51
|
+
| `sudo -n …` | runs only if polkit would authorise without asking; otherwise exit 1 with `sudo: a password is required` |
|
|
52
|
+
| `sudo -v` | performs the authentication, as `sudo -v` does |
|
|
53
|
+
| `sudo -l` | prints the polkit policy and this session's temporary authorizations |
|
|
54
|
+
| `sudo -k`, `-K` | revokes polkit's temporary authorizations |
|
|
55
|
+
| `sudo -H`, `-N`, `-P` | accepted: pkexec already does what they ask |
|
|
56
|
+
| `sudo -s CMD` | runs `CMD` through a shell, as root |
|
|
57
|
+
| `sudo -E …` | runs with the caller's environment |
|
|
58
|
+
| `sudo -b …` | starts the command in the background |
|
|
59
|
+
| `sudo --help`, `-V` | answered by the real sudo; nothing elevates |
|
|
60
|
+
| `sudo -i`, `-S`, `-A` | refused, with the equivalent command |
|
|
53
61
|
| `sudoedit`, `visudo` | refused, with the alternative |
|
|
54
62
|
| `su -c 'id'` | dialog, then runs as root |
|
|
55
63
|
| `su -c 'id' bob` | as above, as `bob` (administrator password) |
|
|
@@ -57,7 +65,9 @@ For a local checkout, run `npm run build` once, then reference the directory:
|
|
|
57
65
|
| `su -m`, `-s`, `-g`, `-w`, `--pty` | refused, with the alternative |
|
|
58
66
|
| `pkexec …` | passed through |
|
|
59
67
|
|
|
60
|
-
|
|
68
|
+
An option is refused only when polkit cannot express it: it needs a terminal, a
|
|
69
|
+
password channel polkit does not have, or an editor. Refusals exit with status 64
|
|
70
|
+
and one line naming the alternative.
|
|
61
71
|
|
|
62
72
|
## How it works
|
|
63
73
|
|
|
@@ -66,6 +76,16 @@ resolves those instead of the system binaries, and they forward the command to
|
|
|
66
76
|
`pkexec`. Command text is never rewritten, so quoting, pipes and subshells need
|
|
67
77
|
no special handling.
|
|
68
78
|
|
|
79
|
+
The `create.before` shell hook prepends the stand-in directory to the spawned
|
|
80
|
+
`PATH`. The directory lives in `$XDG_RUNTIME_DIR` (falling back to `/tmp`) as
|
|
81
|
+
`0700`, is removed when the plugin is unloaded, and also holds
|
|
82
|
+
`opencode-polkit-env`, the helper `sudo -E` uses to re-export an environment
|
|
83
|
+
pkexec stripped.
|
|
84
|
+
|
|
85
|
+
The shell tool's description gains one sentence about the redirect, so the model
|
|
86
|
+
plans privilege with the real contract instead of learning it from a failed
|
|
87
|
+
call.
|
|
88
|
+
|
|
69
89
|
## Authorisation
|
|
70
90
|
|
|
71
91
|
`polkitd` decides whether a password is required. These commands always require
|
|
@@ -79,6 +99,14 @@ session then runs without a dialog. That cache belongs to the agent: another
|
|
|
79
99
|
agent, a restarted agent or a dismissed dialog means the next command prompts
|
|
80
100
|
again, so do not rely on it.
|
|
81
101
|
|
|
102
|
+
Measured on polkit 128 with `polkit-gnome`'s agent: after a successful `pkexec`,
|
|
103
|
+
`pkcheck --list-temp` prints nothing and a non-interactive `pkcheck` still
|
|
104
|
+
reports that authentication is required. polkit records no temporary
|
|
105
|
+
authorization for this action, so the silent second run is the agent answering
|
|
106
|
+
from its own copy of the password, and no user-space query can predict it. That
|
|
107
|
+
is why `sudo -n` reports what the policy says rather than guessing whether a
|
|
108
|
+
dialog would appear, and why `sudo -k` can only clear polkit's own record.
|
|
109
|
+
|
|
82
110
|
## Limitations
|
|
83
111
|
|
|
84
112
|
- Absolute paths (`/usr/bin/sudo`) and relative ones (`./sudo`) bypass `PATH`.
|
|
@@ -87,6 +115,11 @@ again, so do not rely on it.
|
|
|
87
115
|
- `su -c … USER` authenticates an administrator (polkit), not `USER`, so an
|
|
88
116
|
account that real `su` would allow can be refused here.
|
|
89
117
|
- `su -c` runs the command with `sh -c`, not with the target user's login shell.
|
|
118
|
+
- `sudo -E` re-exports the environment through `pkexec`'s argument list, so the
|
|
119
|
+
values are visible in `ps` while the command starts. Exported shell functions
|
|
120
|
+
are skipped, and `PATH` loses the shim directories.
|
|
121
|
+
- `sudo -k` cannot clear the password a desktop agent holds; only polkit's
|
|
122
|
+
temporary authorizations are revoked.
|
|
90
123
|
|
|
91
124
|
These bypass the redirect, not polkit, which still decides whether the command
|
|
92
125
|
runs.
|
|
@@ -98,11 +131,12 @@ without one, `pkexec` fails instead of prompting.
|
|
|
98
131
|
|
|
99
132
|
## Exit status
|
|
100
133
|
|
|
101
|
-
| Status | Meaning
|
|
102
|
-
|
|
103
|
-
|
|
|
104
|
-
|
|
|
105
|
-
|
|
|
134
|
+
| Status | Meaning |
|
|
135
|
+
|--------|--------------------------------------------------------|
|
|
136
|
+
| 1 | `-n` or `-v` could not be satisfied: polkit requires authentication, or refuses |
|
|
137
|
+
| 126 | the polkit dialog was dismissed |
|
|
138
|
+
| 127 | not authorised, or the program could not run |
|
|
139
|
+
| 64 | the plugin refused the invocation |
|
|
106
140
|
|
|
107
141
|
Messages are in English.
|
|
108
142
|
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,16 @@ import { installPolkitShims, removePolkitShims } from "./shims.js";
|
|
|
4
4
|
* Only the prepend matters; the rest is a last-resort fallback.
|
|
5
5
|
*/
|
|
6
6
|
const FALLBACK_PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
|
|
7
|
+
/** Tool names whose description should name how elevation works here. */
|
|
8
|
+
const SHELL_TOOLS = ["bash", "shell"];
|
|
9
|
+
/** Opening words of the appended description; also the guard against appending twice. */
|
|
10
|
+
const CONTRACT_MARKER = "polkit redirect:";
|
|
11
|
+
/**
|
|
12
|
+
* Appended to the shell tool's description. The model plans privilege with what
|
|
13
|
+
* it knows about sudo, so the difference is stated where it reads the tool,
|
|
14
|
+
* instead of being learned from a failed call.
|
|
15
|
+
*/
|
|
16
|
+
const SHELL_TOOL_CONTRACT = `${CONTRACT_MARKER} \`sudo\`, \`doas\` and \`su -c\` run through 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 the authentication, and \`sudo -l\` prints the polkit policy that decides.`;
|
|
7
17
|
/**
|
|
8
18
|
* Redirects sudo/doas/su -c to pkexec without touching the command text.
|
|
9
19
|
*
|
|
@@ -26,6 +36,20 @@ const setup = async (ctx) => {
|
|
|
26
36
|
if (!path.startsWith(prefix))
|
|
27
37
|
event.env.PATH = prefix + path;
|
|
28
38
|
});
|
|
39
|
+
// The shell tool is the host's, so its description is extended rather than
|
|
40
|
+
// replaced. Every reload runs this transform again, so a description that
|
|
41
|
+
// already carries the line is left alone.
|
|
42
|
+
await ctx.tool.transform((tools) => {
|
|
43
|
+
for (const tool of tools.list()) {
|
|
44
|
+
if (!SHELL_TOOLS.includes(tool.id) && !SHELL_TOOLS.includes(tool.name))
|
|
45
|
+
continue;
|
|
46
|
+
tools.update(tool.id, (entry) => {
|
|
47
|
+
if (entry.description.includes(CONTRACT_MARKER))
|
|
48
|
+
return;
|
|
49
|
+
entry.description = `${entry.description}\n\n${SHELL_TOOL_CONTRACT}`;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
});
|
|
29
53
|
return () => removePolkitShims(shimDir);
|
|
30
54
|
};
|
|
31
55
|
/**
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAElE;;;GAGG;AACH,MAAM,aAAa,GAAG,8DAA8D,CAAA;AAEpF;;;;;;;;;;;;GAYG;AACH,MAAM,KAAK,GAAG,KAAK,EAAE,GAAmB,EAA2B,EAAE;IACnE,MAAM,OAAO,GAAG,kBAAkB,EAAE,CAAA;IACpC,MAAM,MAAM,GAAG,GAAG,OAAO,GAAG,CAAA;IAE5B,8EAA8E;IAC9E,MAAM,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC,KAAK,EAAE,EAAE;QAC9C,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,IAAI,aAAa,CAAA;QAC5C,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC;YAAE,KAAK,CAAC,GAAG,CAAC,IAAI,GAAG,MAAM,GAAG,IAAI,CAAA;IAC9D,CAAC,CAAC,CAAA;IAEF,OAAO,GAAG,EAAE,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAA;AACzC,CAAC,CAAA;AAED;;;GAGG;AACH,eAAe,EAAE,EAAE,EAAE,iBAAiB,EAAE,KAAK,EAA0B,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAElE;;;GAGG;AACH,MAAM,aAAa,GAAG,8DAA8D,CAAA;AAEpF,yEAAyE;AACzE,MAAM,WAAW,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAErC,yFAAyF;AACzF,MAAM,eAAe,GAAG,kBAAkB,CAAA;AAE1C;;;;GAIG;AACH,MAAM,mBAAmB,GAAG,GAAG,eAAe,wTAAwT,CAAA;AAEtW;;;;;;;;;;;;GAYG;AACH,MAAM,KAAK,GAAG,KAAK,EAAE,GAAmB,EAA2B,EAAE;IACnE,MAAM,OAAO,GAAG,kBAAkB,EAAE,CAAA;IACpC,MAAM,MAAM,GAAG,GAAG,OAAO,GAAG,CAAA;IAE5B,8EAA8E;IAC9E,MAAM,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC,KAAK,EAAE,EAAE;QAC9C,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,IAAI,aAAa,CAAA;QAC5C,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC;YAAE,KAAK,CAAC,GAAG,CAAC,IAAI,GAAG,MAAM,GAAG,IAAI,CAAA;IAC9D,CAAC,CAAC,CAAA;IAEF,2EAA2E;IAC3E,0EAA0E;IAC1E,0CAA0C;IAC1C,MAAM,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE;QACjC,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;YAChC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;gBAAE,SAAQ;YAChF,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,EAAE;gBAC9B,IAAI,KAAK,CAAC,WAAW,CAAC,QAAQ,CAAC,eAAe,CAAC;oBAAE,OAAM;gBACvD,KAAK,CAAC,WAAW,GAAG,GAAG,KAAK,CAAC,WAAW,OAAO,mBAAmB,EAAE,CAAA;YACtE,CAAC,CAAC,CAAA;QACJ,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,OAAO,GAAG,EAAE,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAA;AACzC,CAAC,CAAA;AAED;;;GAGG;AACH,eAAe,EAAE,EAAE,EAAE,iBAAiB,EAAE,KAAK,EAA0B,CAAA"}
|
package/dist/shims.d.ts
CHANGED
|
@@ -9,7 +9,9 @@ export declare const SHIM_USAGE_EXIT = 64;
|
|
|
9
9
|
* Writes the shim scripts into a fresh private directory and returns it.
|
|
10
10
|
* The caller prepends this directory to PATH; the shell then resolves
|
|
11
11
|
* `sudo`/`doas`/`su` to these files instead of the system ones. The directory
|
|
12
|
-
* is 0700 so no other local user can rewrite the scripts
|
|
12
|
+
* is 0700 so no other local user can rewrite the scripts, and it holds
|
|
13
|
+
* `opencode-polkit-env`, the helper `sudo -E` uses to re-export an environment
|
|
14
|
+
* pkexec stripped.
|
|
13
15
|
*/
|
|
14
16
|
export declare function installPolkitShims(): string;
|
|
15
17
|
/** Deletes a directory created by installPolkitShims. */
|
package/dist/shims.js
CHANGED
|
@@ -8,82 +8,260 @@ import { isAbsolute, join } from "node:path";
|
|
|
8
8
|
* any output.
|
|
9
9
|
*/
|
|
10
10
|
export const SHIM_USAGE_EXIT = 64;
|
|
11
|
+
/** The action pkexec checks; the checks below ask polkit about the same one. */
|
|
12
|
+
const POLKIT_ACTION = "org.freedesktop.policykit.exec";
|
|
11
13
|
/**
|
|
12
|
-
* The
|
|
13
|
-
*
|
|
14
|
-
* and never parses what pkexec prints: the exit status is the whole result.
|
|
15
|
-
*
|
|
16
|
-
* Only options with an exact pkexec equivalent are translated. Everything
|
|
17
|
-
* else is refused with an actionable message rather than forwarded, because
|
|
18
|
-
* pkexec would reject it with a message that does not say what to do instead.
|
|
14
|
+
* The part every shim starts with: one refusal helper, and a check that the two
|
|
15
|
+
* polkit tools exist at all.
|
|
19
16
|
*/
|
|
20
|
-
const
|
|
21
|
-
# opencode-polkit: sudo shim. Routes sudo to pkexec so polkit can authorize it.
|
|
22
|
-
# Generated at plugin startup; do not edit.
|
|
23
|
-
|
|
24
|
-
fail() {
|
|
17
|
+
const SHIM_PRELUDE = `fail() {
|
|
25
18
|
printf 'opencode-polkit: %s\\n' "$1" >&2
|
|
26
19
|
exit ${SHIM_USAGE_EXIT}
|
|
27
20
|
}
|
|
28
21
|
|
|
29
22
|
command -v pkexec >/dev/null 2>&1 || fail 'pkexec is not installed; install polkit'
|
|
23
|
+
command -v pkcheck >/dev/null 2>&1 || fail 'pkcheck is not installed; install polkit'
|
|
24
|
+
`;
|
|
25
|
+
/**
|
|
26
|
+
* The checks that answer what sudo's timestamp file used to answer, plus the
|
|
27
|
+
* pkexec wrapper. Only exit statuses are read: pkcheck's messages and its
|
|
28
|
+
* `polkit.result` detail are localised and addressed to humans, so parsing them
|
|
29
|
+
* would break on the next locale or polkit release.
|
|
30
|
+
*/
|
|
31
|
+
const SHIM_CHECKS = `# The action pkexec checks: a pkcheck verdict on it describes what the next
|
|
32
|
+
# pkexec will do, which is how the shims answer what sudo's timestamp answered.
|
|
33
|
+
ACTION=${POLKIT_ACTION}
|
|
30
34
|
|
|
35
|
+
# pkcheck(1) warns that a bare pid races with pid reuse, so pin the start time.
|
|
36
|
+
subject() {
|
|
37
|
+
started=$(awk '{ print $22 }' /proc/$$/stat 2>/dev/null)
|
|
38
|
+
if [ -n "$started" ]; then
|
|
39
|
+
printf '%s,%s,%s' "$$" "$started" "$(id -u)"
|
|
40
|
+
else
|
|
41
|
+
printf '%s' "$$"
|
|
42
|
+
fi
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# 0 grants the action without asking anyone, 2 requires authentication,
|
|
46
|
+
# 1 refuses it. pkcheck's own 126 (usage) and 127 (error) pass through.
|
|
47
|
+
check_without_prompt() {
|
|
48
|
+
pkcheck --action-id "$ACTION" --process "$(subject)" >/dev/null 2>&1
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# The same check with the desktop agent allowed to answer. An agent that still
|
|
52
|
+
# holds the password answers without a dialog, which is why a 0 here also means
|
|
53
|
+
# the next pkexec runs as root without asking.
|
|
54
|
+
check_with_prompt() {
|
|
55
|
+
pkcheck --action-id "$ACTION" --process "$(subject)" --allow-user-interaction >/dev/null 2>&1
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# -n means "run only if no one has to be asked". polkit cannot say whether the
|
|
59
|
+
# agent would answer from its cache, so a 2 is reported as needing a password
|
|
60
|
+
# and the command is not run: that is what -n promises, in both directions.
|
|
61
|
+
require_without_prompt() {
|
|
62
|
+
if [ -n "$noninteractive" ]; then
|
|
63
|
+
check_without_prompt
|
|
64
|
+
status=$?
|
|
65
|
+
case $status in
|
|
66
|
+
0)
|
|
67
|
+
return 0
|
|
68
|
+
;;
|
|
69
|
+
1)
|
|
70
|
+
printf 'opencode-polkit: %s: polkit denies %s to this process\\n' "$PROG" "$ACTION" >&2
|
|
71
|
+
exit 1
|
|
72
|
+
;;
|
|
73
|
+
2)
|
|
74
|
+
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
|
|
75
|
+
exit 1
|
|
76
|
+
;;
|
|
77
|
+
*)
|
|
78
|
+
printf 'opencode-polkit: %s: polkit check failed (status %s)\\n' "$PROG" "$status" >&2
|
|
79
|
+
exit 127
|
|
80
|
+
;;
|
|
81
|
+
esac
|
|
82
|
+
fi
|
|
83
|
+
return 0
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
# pkexec is silent when the dialog is dismissed (status 126), so say what the
|
|
87
|
+
# status means. Every other failure path already prints its own reason.
|
|
88
|
+
run_pkexec() {
|
|
89
|
+
pkexec "$@"
|
|
90
|
+
status=$?
|
|
91
|
+
if [ $status -eq 126 ]; then
|
|
92
|
+
printf 'opencode-polkit: %s\\n' 'the polkit authentication dialog was dismissed' >&2
|
|
93
|
+
fi
|
|
94
|
+
exit $status
|
|
95
|
+
}
|
|
96
|
+
`;
|
|
97
|
+
/**
|
|
98
|
+
* The sudo shim. Lives on PATH ahead of the real sudo, so the shell — not this
|
|
99
|
+
* plugin — decides that `sudo` means this file. It forwards to pkexec and
|
|
100
|
+
* never parses what pkexec prints: the exit status is the whole result.
|
|
101
|
+
*
|
|
102
|
+
* Options are handled in four ways, and only the last one refuses. Those whose
|
|
103
|
+
* effect pkexec already has (`-H`, `-N`, `-P`) are accepted as no-ops; those
|
|
104
|
+
* polkit answers better than a run (`-n`, `-v`, `-l`, `-k`) become checks; those
|
|
105
|
+
* a wrapper can express (`-s COMMAND`, `-E`, `-b`) are translated; the rest,
|
|
106
|
+
* which need a terminal or a password channel polkit does not have, are refused
|
|
107
|
+
* with one line naming the alternative.
|
|
108
|
+
*/
|
|
109
|
+
const SUDO_SHIM = `#!/bin/sh
|
|
110
|
+
# opencode-polkit: sudo shim. Routes sudo to pkexec so polkit can authorize it.
|
|
111
|
+
# Generated at plugin startup; do not edit.
|
|
112
|
+
|
|
113
|
+
${SHIM_PRELUDE}
|
|
114
|
+
${SHIM_CHECKS}
|
|
115
|
+
PROG=sudo
|
|
31
116
|
user=
|
|
117
|
+
noninteractive=
|
|
118
|
+
preserve=
|
|
119
|
+
preserve_names=
|
|
120
|
+
background=
|
|
121
|
+
mode=
|
|
122
|
+
|
|
32
123
|
while [ $# -gt 0 ]; do
|
|
33
124
|
case $1 in
|
|
34
125
|
--)
|
|
35
126
|
shift
|
|
36
127
|
break
|
|
37
128
|
;;
|
|
38
|
-
-u)
|
|
39
|
-
[ $# -ge 2 ] || fail 'sudo -u needs a username: -u USER PROGRAM'
|
|
40
|
-
user=$2
|
|
41
|
-
shift 2
|
|
42
|
-
;;
|
|
43
129
|
--user)
|
|
44
130
|
[ $# -ge 2 ] || fail 'sudo --user needs a username: --user USER PROGRAM'
|
|
45
131
|
user=$2
|
|
46
132
|
shift 2
|
|
47
133
|
;;
|
|
48
|
-
-u?*)
|
|
49
|
-
user=\${1#-u}
|
|
50
|
-
[ -n "$user" ] || fail 'sudo -u needs a username: -u USER PROGRAM'
|
|
51
|
-
shift
|
|
52
|
-
;;
|
|
53
134
|
--user=*)
|
|
54
135
|
user=\${1#--user=}
|
|
55
|
-
[ -n "$user" ] || fail 'sudo --user needs a username: --user USER PROGRAM'
|
|
56
136
|
shift
|
|
57
137
|
;;
|
|
58
|
-
|
|
59
|
-
|
|
138
|
+
--non-interactive)
|
|
139
|
+
noninteractive=1
|
|
140
|
+
shift
|
|
141
|
+
;;
|
|
142
|
+
--preserve-env)
|
|
143
|
+
preserve=1
|
|
144
|
+
shift
|
|
145
|
+
;;
|
|
146
|
+
--preserve-env=*)
|
|
147
|
+
preserve=1
|
|
148
|
+
preserve_names=\${1#--preserve-env=}
|
|
149
|
+
shift
|
|
150
|
+
;;
|
|
151
|
+
--set-home | --no-update | --preserve-groups)
|
|
152
|
+
shift
|
|
60
153
|
;;
|
|
61
|
-
|
|
62
|
-
|
|
154
|
+
--list)
|
|
155
|
+
mode=list
|
|
156
|
+
shift
|
|
63
157
|
;;
|
|
64
|
-
|
|
65
|
-
|
|
158
|
+
--validate)
|
|
159
|
+
mode=validate
|
|
160
|
+
shift
|
|
66
161
|
;;
|
|
67
|
-
-
|
|
68
|
-
|
|
162
|
+
--reset-timestamp | --remove-timestamp)
|
|
163
|
+
mode=revoke
|
|
164
|
+
shift
|
|
69
165
|
;;
|
|
70
|
-
|
|
71
|
-
|
|
166
|
+
--background)
|
|
167
|
+
background=1
|
|
168
|
+
shift
|
|
72
169
|
;;
|
|
73
|
-
|
|
74
|
-
|
|
170
|
+
--help)
|
|
171
|
+
mode=help
|
|
172
|
+
shift
|
|
75
173
|
;;
|
|
76
|
-
|
|
77
|
-
|
|
174
|
+
--version)
|
|
175
|
+
mode=version
|
|
176
|
+
shift
|
|
78
177
|
;;
|
|
79
|
-
|
|
80
|
-
|
|
178
|
+
--stdin | --askpass | --edit | --login)
|
|
179
|
+
case $1 in
|
|
180
|
+
--edit)
|
|
181
|
+
fail 'sudoedit is not supported; write a temp file and run pkexec install or pkexec cp on it.'
|
|
182
|
+
;;
|
|
183
|
+
--stdin)
|
|
184
|
+
fail 'sudo -S reads the password from stdin; pkexec authenticates through the polkit agent. Drop -S.'
|
|
185
|
+
;;
|
|
186
|
+
--askpass)
|
|
187
|
+
fail 'SUDO_ASKPASS does not apply to pkexec; drop -A.'
|
|
188
|
+
;;
|
|
189
|
+
*)
|
|
190
|
+
fail 'sudo -i opens a login shell; run pkexec bash instead.'
|
|
191
|
+
;;
|
|
192
|
+
esac
|
|
193
|
+
;;
|
|
194
|
+
--shell)
|
|
195
|
+
mode=shell
|
|
196
|
+
shift
|
|
81
197
|
;;
|
|
82
|
-
|
|
83
|
-
fail
|
|
198
|
+
--*)
|
|
199
|
+
fail "option has no pkexec equivalent: $1 (pkexec accepts --user, --keep-cwd, --disable-internal-agent)"
|
|
84
200
|
;;
|
|
85
201
|
-?*)
|
|
86
|
-
|
|
202
|
+
rest=\${1#-}
|
|
203
|
+
shift
|
|
204
|
+
while [ -n "$rest" ]; do
|
|
205
|
+
letter=\${rest%"\${rest#?}"}
|
|
206
|
+
rest=\${rest#?}
|
|
207
|
+
case $letter in
|
|
208
|
+
u)
|
|
209
|
+
if [ -n "$rest" ]; then
|
|
210
|
+
user=$rest
|
|
211
|
+
rest=
|
|
212
|
+
else
|
|
213
|
+
[ $# -ge 1 ] || fail 'sudo -u needs a username: -u USER PROGRAM'
|
|
214
|
+
user=$1
|
|
215
|
+
shift
|
|
216
|
+
fi
|
|
217
|
+
;;
|
|
218
|
+
n)
|
|
219
|
+
noninteractive=1
|
|
220
|
+
;;
|
|
221
|
+
E)
|
|
222
|
+
preserve=1
|
|
223
|
+
;;
|
|
224
|
+
H | N | P)
|
|
225
|
+
# pkexec already sets HOME, keeps no timestamp and leaves groups alone.
|
|
226
|
+
;;
|
|
227
|
+
b)
|
|
228
|
+
background=1
|
|
229
|
+
;;
|
|
230
|
+
l)
|
|
231
|
+
mode=list
|
|
232
|
+
;;
|
|
233
|
+
v)
|
|
234
|
+
mode=validate
|
|
235
|
+
;;
|
|
236
|
+
k | K)
|
|
237
|
+
mode=revoke
|
|
238
|
+
;;
|
|
239
|
+
s)
|
|
240
|
+
mode=shell
|
|
241
|
+
;;
|
|
242
|
+
h)
|
|
243
|
+
mode=help
|
|
244
|
+
;;
|
|
245
|
+
V)
|
|
246
|
+
mode=version
|
|
247
|
+
;;
|
|
248
|
+
i)
|
|
249
|
+
fail 'sudo -i opens a login shell; run pkexec bash instead.'
|
|
250
|
+
;;
|
|
251
|
+
e)
|
|
252
|
+
fail 'sudoedit is not supported; write a temp file and run pkexec install or pkexec cp on it.'
|
|
253
|
+
;;
|
|
254
|
+
S)
|
|
255
|
+
fail 'sudo -S reads the password from stdin; pkexec authenticates through the polkit agent. Drop -S.'
|
|
256
|
+
;;
|
|
257
|
+
A)
|
|
258
|
+
fail 'SUDO_ASKPASS does not apply to pkexec; drop -A.'
|
|
259
|
+
;;
|
|
260
|
+
*)
|
|
261
|
+
fail "option has no pkexec equivalent: -$letter (pkexec accepts --user, --keep-cwd, --disable-internal-agent)"
|
|
262
|
+
;;
|
|
263
|
+
esac
|
|
264
|
+
done
|
|
87
265
|
;;
|
|
88
266
|
*)
|
|
89
267
|
break
|
|
@@ -91,20 +269,148 @@ while [ $# -gt 0 ]; do
|
|
|
91
269
|
esac
|
|
92
270
|
done
|
|
93
271
|
|
|
94
|
-
#
|
|
95
|
-
#
|
|
96
|
-
|
|
97
|
-
|
|
272
|
+
# --help and --version describe the binary, not the authorisation, so nothing is
|
|
273
|
+
# elevated: answering them exactly keeps scripts that probe sudo working.
|
|
274
|
+
if [ "$mode" = help ] || [ "$mode" = version ]; then
|
|
275
|
+
for candidate in /usr/bin/sudo /bin/sudo /usr/local/bin/sudo; do
|
|
276
|
+
[ -x "$candidate" ] || continue
|
|
277
|
+
if [ "$mode" = help ]; then
|
|
278
|
+
exec "$candidate" --help
|
|
279
|
+
fi
|
|
280
|
+
exec "$candidate" --version
|
|
281
|
+
done
|
|
282
|
+
fail 'the real sudo is not installed, so --help and --version cannot be answered'
|
|
283
|
+
fi
|
|
284
|
+
|
|
285
|
+
# -l reports the policy polkit will apply and the authorizations this session
|
|
286
|
+
# already holds. Both halves are local and never prompt.
|
|
287
|
+
if [ "$mode" = list ]; then
|
|
288
|
+
pkaction --action-id "$ACTION" --verbose || exit 127
|
|
289
|
+
printf '\\nopencode-polkit: temporary authorizations in this session\\n'
|
|
290
|
+
pkcheck --list-temp || exit 127
|
|
291
|
+
exit 0
|
|
292
|
+
fi
|
|
293
|
+
|
|
294
|
+
# -v asks the same question sudo -v asks: can authentication succeed right now?
|
|
295
|
+
if [ "$mode" = validate ]; then
|
|
296
|
+
if [ -n "$noninteractive" ]; then
|
|
297
|
+
check_without_prompt
|
|
298
|
+
else
|
|
299
|
+
check_with_prompt
|
|
300
|
+
fi
|
|
98
301
|
status=$?
|
|
99
|
-
|
|
100
|
-
|
|
302
|
+
case $status in
|
|
303
|
+
0)
|
|
304
|
+
exit 0
|
|
305
|
+
;;
|
|
306
|
+
1)
|
|
307
|
+
printf 'opencode-polkit: sudo: polkit denies %s to this process\\n' "$ACTION" >&2
|
|
308
|
+
exit 1
|
|
309
|
+
;;
|
|
310
|
+
2)
|
|
311
|
+
if [ -n "$noninteractive" ]; then
|
|
312
|
+
printf 'sudo: a password is required (polkit requires authentication for %s)\\n' "$ACTION" >&2
|
|
313
|
+
else
|
|
314
|
+
printf 'opencode-polkit: sudo: no polkit authentication agent answered\\n' >&2
|
|
315
|
+
fi
|
|
316
|
+
exit 1
|
|
317
|
+
;;
|
|
318
|
+
3)
|
|
319
|
+
printf 'opencode-polkit: %s\\n' 'the polkit authentication dialog was dismissed' >&2
|
|
320
|
+
exit 126
|
|
321
|
+
;;
|
|
322
|
+
*)
|
|
323
|
+
printf 'opencode-polkit: sudo: polkit check failed (status %s)\\n' "$status" >&2
|
|
324
|
+
exit 127
|
|
325
|
+
;;
|
|
326
|
+
esac
|
|
327
|
+
fi
|
|
328
|
+
|
|
329
|
+
# -k drops what polkit itself recorded. The password a desktop agent keeps is
|
|
330
|
+
# the agent's, not polkit's, so say that it stays.
|
|
331
|
+
if [ "$mode" = revoke ]; then
|
|
332
|
+
pkcheck --revoke-temp || exit 127
|
|
333
|
+
if [ $# -eq 0 ]; then
|
|
334
|
+
printf 'opencode-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
|
|
335
|
+
exit 0
|
|
101
336
|
fi
|
|
102
|
-
|
|
337
|
+
fi
|
|
338
|
+
|
|
339
|
+
# -E: pkexec strips the environment, so name the variables again inside the shell
|
|
340
|
+
# that runs the command. Entries reach the helper as arguments, so spaces, quotes
|
|
341
|
+
# and newlines survive exactly; the helper also drops every shim directory from
|
|
342
|
+
# PATH, so an elevated command cannot resolve sudo back to a shim.
|
|
343
|
+
shim_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd 2>/dev/null)
|
|
344
|
+
env_helper=$shim_dir/opencode-polkit-env
|
|
345
|
+
[ -x "$env_helper" ] || fail "the opencode-polkit environment helper is missing: $env_helper"
|
|
346
|
+
|
|
347
|
+
# Runs in the caller's shell, never in a command substitution: an exit there
|
|
348
|
+
# would only end the subshell, and a name reaches eval below, so it must be a
|
|
349
|
+
# name before that happens.
|
|
350
|
+
check_preserve() {
|
|
351
|
+
if [ -z "$preserve_names" ]; then
|
|
352
|
+
command -v xargs >/dev/null 2>&1 || fail '-E needs xargs to forward the environment; name the variables instead: --preserve-env=NAME[,NAME...]'
|
|
353
|
+
return 0
|
|
354
|
+
fi
|
|
355
|
+
old_ifs=$IFS
|
|
356
|
+
IFS=,
|
|
357
|
+
for name in $preserve_names; do
|
|
358
|
+
case $name in
|
|
359
|
+
'' | [0-9]* | *[!A-Za-z0-9_]*)
|
|
360
|
+
IFS=$old_ifs
|
|
361
|
+
fail "not a variable name: $name"
|
|
362
|
+
;;
|
|
363
|
+
esac
|
|
364
|
+
done
|
|
365
|
+
IFS=$old_ifs
|
|
103
366
|
}
|
|
104
367
|
|
|
368
|
+
export_prefix() {
|
|
369
|
+
if [ -n "$preserve_names" ]; then
|
|
370
|
+
set --
|
|
371
|
+
old_ifs=$IFS
|
|
372
|
+
IFS=,
|
|
373
|
+
for name in $preserve_names; do
|
|
374
|
+
is_set=
|
|
375
|
+
eval "is_set=\\\${$name+yes}"
|
|
376
|
+
[ -n "$is_set" ] || continue
|
|
377
|
+
eval "value=\\\${$name}"
|
|
378
|
+
set -- "$@" "$name=$value"
|
|
379
|
+
done
|
|
380
|
+
IFS=$old_ifs
|
|
381
|
+
"$env_helper" "$@"
|
|
382
|
+
return
|
|
383
|
+
fi
|
|
384
|
+
env -0 | xargs -0 -n 64 "$env_helper"
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
require_without_prompt
|
|
388
|
+
|
|
389
|
+
[ $# -gt 0 ] || fail 'sudo needs a command: sudo [OPTIONS] COMMAND (an interactive shell needs a terminal)'
|
|
390
|
+
|
|
391
|
+
# -s runs the command through a shell, which pkexec expresses exactly.
|
|
392
|
+
if [ "$mode" = shell ]; then
|
|
393
|
+
set -- /bin/sh -c "$*"
|
|
394
|
+
fi
|
|
395
|
+
|
|
396
|
+
# The preserved environment is exported by a shell prologue, so the command keeps
|
|
397
|
+
# its own argv rather than being flattened into one string.
|
|
398
|
+
if [ -n "$preserve" ]; then
|
|
399
|
+
check_preserve
|
|
400
|
+
prefix=$(export_prefix)
|
|
401
|
+
set -- /bin/sh -c "\${prefix}exec \\"\\$@\\"" sh "$@"
|
|
402
|
+
fi
|
|
403
|
+
|
|
105
404
|
if [ -n "$user" ]; then
|
|
106
|
-
|
|
405
|
+
set -- --user "$user" "$@"
|
|
406
|
+
fi
|
|
407
|
+
|
|
408
|
+
# -b returns as soon as the command is started, as sudo -b does.
|
|
409
|
+
if [ -n "$background" ]; then
|
|
410
|
+
pkexec "$@" &
|
|
411
|
+
exit 0
|
|
107
412
|
fi
|
|
413
|
+
|
|
108
414
|
run_pkexec "$@"
|
|
109
415
|
`;
|
|
110
416
|
/** The doas shim: same contract, doas option grammar. */
|
|
@@ -112,14 +418,13 @@ const DOAS_SHIM = `#!/bin/sh
|
|
|
112
418
|
# opencode-polkit: doas shim. Routes doas to pkexec so polkit can authorize it.
|
|
113
419
|
# Generated at plugin startup; do not edit.
|
|
114
420
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
command -v pkexec >/dev/null 2>&1 || fail 'pkexec is not installed; install polkit'
|
|
121
|
-
|
|
421
|
+
${SHIM_PRELUDE}
|
|
422
|
+
${SHIM_CHECKS}
|
|
423
|
+
PROG=doas
|
|
122
424
|
user=
|
|
425
|
+
noninteractive=
|
|
426
|
+
mode=
|
|
427
|
+
|
|
123
428
|
while [ $# -gt 0 ]; do
|
|
124
429
|
case $1 in
|
|
125
430
|
--)
|
|
@@ -133,14 +438,15 @@ while [ $# -gt 0 ]; do
|
|
|
133
438
|
;;
|
|
134
439
|
-u?*)
|
|
135
440
|
user=\${1#-u}
|
|
136
|
-
[ -n "$user" ] || fail 'doas -u needs a username: -u USER PROGRAM'
|
|
137
441
|
shift
|
|
138
442
|
;;
|
|
139
443
|
-n)
|
|
140
|
-
|
|
444
|
+
noninteractive=1
|
|
445
|
+
shift
|
|
141
446
|
;;
|
|
142
447
|
-s)
|
|
143
|
-
|
|
448
|
+
mode=shell
|
|
449
|
+
shift
|
|
144
450
|
;;
|
|
145
451
|
-C | -a | -L)
|
|
146
452
|
fail "option has no pkexec equivalent: $1"
|
|
@@ -154,20 +460,18 @@ while [ $# -gt 0 ]; do
|
|
|
154
460
|
esac
|
|
155
461
|
done
|
|
156
462
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
fi
|
|
165
|
-
exit $status
|
|
166
|
-
}
|
|
463
|
+
require_without_prompt
|
|
464
|
+
|
|
465
|
+
[ $# -gt 0 ] || fail 'doas needs a command: doas [OPTIONS] COMMAND (an interactive shell needs a terminal)'
|
|
466
|
+
|
|
467
|
+
if [ "$mode" = shell ]; then
|
|
468
|
+
set -- /bin/sh -c "$*"
|
|
469
|
+
fi
|
|
167
470
|
|
|
168
471
|
if [ -n "$user" ]; then
|
|
169
|
-
|
|
472
|
+
set -- --user "$user" "$@"
|
|
170
473
|
fi
|
|
474
|
+
|
|
171
475
|
run_pkexec "$@"
|
|
172
476
|
`;
|
|
173
477
|
/**
|
|
@@ -180,13 +484,9 @@ const SU_SHIM = `#!/bin/sh
|
|
|
180
484
|
# opencode-polkit: su shim. Routes su -c to pkexec so polkit can authorize it.
|
|
181
485
|
# Generated at plugin startup; do not edit.
|
|
182
486
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
command -v pkexec >/dev/null 2>&1 || fail 'pkexec is not installed; install polkit'
|
|
189
|
-
|
|
487
|
+
${SHIM_PRELUDE}
|
|
488
|
+
${SHIM_CHECKS}
|
|
489
|
+
PROG=su
|
|
190
490
|
user=
|
|
191
491
|
cmd=
|
|
192
492
|
has_cmd=
|
|
@@ -234,7 +534,7 @@ while [ $# -gt 0 ]; do
|
|
|
234
534
|
fail 'su -w whitelists environment variables; pkexec sanitizes the environment.'
|
|
235
535
|
;;
|
|
236
536
|
-P | --pty)
|
|
237
|
-
fail 'su --pty asks for a terminal; the
|
|
537
|
+
fail 'su --pty asks for a terminal; the bash tool has none.'
|
|
238
538
|
;;
|
|
239
539
|
-f | --fast)
|
|
240
540
|
fail 'su -f is a shell flag; pass it to the shell in the command instead.'
|
|
@@ -267,24 +567,68 @@ done
|
|
|
267
567
|
[ -n "$has_cmd" ] || fail 'su without -c opens an interactive shell; run pkexec bash instead.'
|
|
268
568
|
|
|
269
569
|
# su keeps the working directory unless it is a login shell, and --keep-cwd is
|
|
270
|
-
# how pkexec does the same.
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
570
|
+
# how pkexec does the same.
|
|
571
|
+
run_pkexec --keep-cwd --user "$user" sh -c "$cmd"
|
|
572
|
+
`;
|
|
573
|
+
/**
|
|
574
|
+
* The environment helper: turns `NAME=VALUE` arguments into an export statement
|
|
575
|
+
* each, so `-E` can rebuild the environment pkexec stripped. Values arrive as
|
|
576
|
+
* arguments rather than text, so spaces, quotes and newlines need no escaping on
|
|
577
|
+
* the way in. PATH keeps every entry except the shim directories.
|
|
578
|
+
*/
|
|
579
|
+
const POLKIT_ENV_HELPER = `#!/bin/sh
|
|
580
|
+
# opencode-polkit: rebuilds a stripped environment. Generated at plugin startup;
|
|
581
|
+
# do not edit.
|
|
582
|
+
|
|
583
|
+
for entry in "$@"; do
|
|
584
|
+
name=\${entry%%=*}
|
|
585
|
+
value=\${entry#*=}
|
|
586
|
+
case $name in
|
|
587
|
+
'' | [0-9]* | *[!A-Za-z0-9_]*)
|
|
588
|
+
continue
|
|
589
|
+
;;
|
|
590
|
+
PATH)
|
|
591
|
+
filtered=
|
|
592
|
+
old_ifs=$IFS
|
|
593
|
+
IFS=:
|
|
594
|
+
for dir in $value; do
|
|
595
|
+
case \${dir##*/} in
|
|
596
|
+
opencode-polkit-*)
|
|
597
|
+
continue
|
|
598
|
+
;;
|
|
599
|
+
esac
|
|
600
|
+
filtered=$filtered\${filtered:+:}$dir
|
|
601
|
+
done
|
|
602
|
+
IFS=$old_ifs
|
|
603
|
+
value=$filtered
|
|
604
|
+
;;
|
|
605
|
+
esac
|
|
606
|
+
quote="'"
|
|
607
|
+
quoted=
|
|
608
|
+
while :; do
|
|
609
|
+
case $value in
|
|
610
|
+
*"$quote"*)
|
|
611
|
+
quoted=$quoted\${value%%"$quote"*}"'\\\\''"
|
|
612
|
+
value=\${value#*"$quote"}
|
|
613
|
+
;;
|
|
614
|
+
*)
|
|
615
|
+
quoted=$quoted$value
|
|
616
|
+
break
|
|
617
|
+
;;
|
|
618
|
+
esac
|
|
619
|
+
done
|
|
620
|
+
printf "export '%s=%s'; " "$name" "$quoted"
|
|
621
|
+
done
|
|
278
622
|
`;
|
|
279
623
|
/** sudoedit edits through sudo's own temporary-file flow; polkit has none. */
|
|
280
624
|
const SUDOEDIT_SHIM = `#!/bin/sh
|
|
281
|
-
|
|
282
|
-
|
|
625
|
+
${SHIM_PRELUDE}
|
|
626
|
+
fail 'sudoedit is not supported; write a temp file and run pkexec install or pkexec cp on it.'
|
|
283
627
|
`;
|
|
284
628
|
/** visudo needs an interactive editor on a terminal the shell tool does not have. */
|
|
285
629
|
const VISUDO_SHIM = `#!/bin/sh
|
|
286
|
-
|
|
287
|
-
|
|
630
|
+
${SHIM_PRELUDE}
|
|
631
|
+
fail 'visudo needs an interactive terminal; validate a candidate file with pkexec visudo -c -f FILE instead.'
|
|
288
632
|
`;
|
|
289
633
|
/** File name → script body. Names must match the commands being redirected. */
|
|
290
634
|
const POLKIT_SHIM_SCRIPTS = {
|
|
@@ -293,12 +637,15 @@ const POLKIT_SHIM_SCRIPTS = {
|
|
|
293
637
|
su: SU_SHIM,
|
|
294
638
|
sudoedit: SUDOEDIT_SHIM,
|
|
295
639
|
visudo: VISUDO_SHIM,
|
|
640
|
+
"opencode-polkit-env": POLKIT_ENV_HELPER,
|
|
296
641
|
};
|
|
297
642
|
/**
|
|
298
643
|
* Writes the shim scripts into a fresh private directory and returns it.
|
|
299
644
|
* The caller prepends this directory to PATH; the shell then resolves
|
|
300
645
|
* `sudo`/`doas`/`su` to these files instead of the system ones. The directory
|
|
301
|
-
* is 0700 so no other local user can rewrite the scripts
|
|
646
|
+
* is 0700 so no other local user can rewrite the scripts, and it holds
|
|
647
|
+
* `opencode-polkit-env`, the helper `sudo -E` uses to re-export an environment
|
|
648
|
+
* pkexec stripped.
|
|
302
649
|
*/
|
|
303
650
|
export function installPolkitShims() {
|
|
304
651
|
const directory = mkdtempSync(join(shimBaseDirectory(), "opencode-polkit-"));
|
package/dist/shims.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shims.js","sourceRoot":"","sources":["../src/shims.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AACnF,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAChC,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAE5C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,EAAE,CAAA;AAEjC
|
|
1
|
+
{"version":3,"file":"shims.js","sourceRoot":"","sources":["../src/shims.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AACnF,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAChC,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAE5C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,EAAE,CAAA;AAEjC,gFAAgF;AAChF,MAAM,aAAa,GAAG,gCAAgC,CAAA;AAEtD;;;GAGG;AACH,MAAM,YAAY,GAAG;;QAEb,eAAe;;;;;CAKtB,CAAA;AAED;;;;;GAKG;AACH,MAAM,WAAW,GAAG;;SAEX,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+DrB,CAAA;AAED;;;;;;;;;;;GAWG;AACH,MAAM,SAAS,GAAG;;;;EAIhB,YAAY;EACZ,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6SZ,CAAA;AAED,yDAAyD;AACzD,MAAM,SAAS,GAAG;;;;EAIhB,YAAY;EACZ,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsDZ,CAAA;AAED;;;;;GAKG;AACH,MAAM,OAAO,GAAG;;;;EAId,YAAY;EACZ,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoFZ,CAAA;AAED;;;;;GAKG;AACH,MAAM,iBAAiB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2CzB,CAAA;AAGD,8EAA8E;AAC9E,MAAM,aAAa,GAAG;EACpB,YAAY;;CAEb,CAAA;AAED,qFAAqF;AACrF,MAAM,WAAW,GAAG;EAClB,YAAY;;CAEb,CAAA;AAED,+EAA+E;AAC/E,MAAM,mBAAmB,GAA2B;IAClD,IAAI,EAAE,SAAS;IACf,IAAI,EAAE,SAAS;IACf,EAAE,EAAE,OAAO;IACX,QAAQ,EAAE,aAAa;IACvB,MAAM,EAAE,WAAW;IACnB,qBAAqB,EAAE,iBAAiB;CACzC,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB;IAChC,MAAM,SAAS,GAAG,WAAW,CAAC,IAAI,CAAC,iBAAiB,EAAE,EAAE,kBAAkB,CAAC,CAAC,CAAA;IAC5E,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,mBAAmB,CAAC,EAAE,CAAC;QACjE,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;QAClC,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;QAC3B,mEAAmE;QACnE,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IACxB,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC;AAED;;;GAGG;AACH,SAAS,iBAAiB;IACxB,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAA;IAC3C,OAAO,OAAO,IAAI,UAAU,CAAC,OAAO,CAAC,IAAI,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,EAAE,CAAA;AACnF,CAAC;AAED,yDAAyD;AACzD,MAAM,UAAU,iBAAiB,CAAC,SAAiB;IACjD,MAAM,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;AACrD,CAAC"}
|
package/package.json
CHANGED