mirror of https://github.com/ospab/ostp.git
Compare commits
4 Commits
1ab55fcdd0
...
f5a1c17679
| Author | SHA1 | Date |
|---|---|---|
|
|
f5a1c17679 | |
|
|
732d0bf5ae | |
|
|
2887f1af8d | |
|
|
b18852ac07 |
|
|
@ -2,5 +2,5 @@
|
||||||
"target_version": "0.4.5",
|
"target_version": "0.4.5",
|
||||||
"branch": "beta",
|
"branch": "beta",
|
||||||
"alpha_iteration": 0,
|
"alpha_iteration": 0,
|
||||||
"beta_iteration": 3
|
"beta_iteration": 5
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 0.4.5+34
|
version: 0.4.5+36
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.11.4
|
sdk: ^3.11.4
|
||||||
|
|
|
||||||
|
|
@ -962,19 +962,47 @@ fn helper_task_command() -> Option<String> {
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
fn helper_task_matches(exe: &std::path::Path) -> bool {
|
fn helper_task_matches(exe: &std::path::Path) -> bool {
|
||||||
let Some(registered) = helper_task_command() else {
|
let Some(registered) = helper_task_command() else {
|
||||||
|
diag_log("task: schtasks /Query returned nothing usable — no task, or its XML had no <Command>");
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
let registered = registered.trim().trim_matches('"');
|
let registered = registered.trim().trim_matches('"');
|
||||||
|
let path = std::path::Path::new(registered);
|
||||||
|
|
||||||
// Canonicalize both sides when possible so `..`, short 8.3 names and
|
// Requiring the registered path to equal the helper we would have launched
|
||||||
// casing differences do not read as a mismatch. A missing file cannot be
|
// was too strict, and bought nothing. What the check exists to catch is a
|
||||||
// canonicalized — which is itself a mismatch worth re-registering over.
|
// task left pointing at a binary that is gone — `schtasks /Run` reports
|
||||||
match (
|
// success merely for accepting such a request, so the app would then wait
|
||||||
std::fs::canonicalize(registered),
|
// on a helper that never starts. Testing that the file exists catches
|
||||||
std::fs::canonicalize(exe),
|
// exactly that, while a task registered by the installer against an
|
||||||
) {
|
// equivalent copy of the helper no longer costs the user a prompt.
|
||||||
(Ok(a), Ok(b)) => a == b,
|
let same_program = path
|
||||||
_ => registered.eq_ignore_ascii_case(&exe.display().to_string()),
|
.file_name()
|
||||||
|
.map(|n| n.eq_ignore_ascii_case(HELPER_EXE_NAME))
|
||||||
|
.unwrap_or(false);
|
||||||
|
let exists = path.is_file();
|
||||||
|
|
||||||
|
diag_log(&format!(
|
||||||
|
"task: registered={registered:?} exists={exists} same_program={same_program} wanted={:?}",
|
||||||
|
exe.display().to_string()
|
||||||
|
));
|
||||||
|
exists && same_program
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Appends a line to a small log beside the helper's argument file.
|
||||||
|
///
|
||||||
|
/// The GUI is a windowed binary with no console, so every `eprintln!` on this
|
||||||
|
/// path went nowhere — which left the one decision that matters, whether the
|
||||||
|
/// scheduled task gets used or the user gets a consent prompt, completely
|
||||||
|
/// unobservable from a user's machine.
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
fn diag_log(msg: &str) {
|
||||||
|
let path = helper_args_file().with_file_name("helper-launch.log");
|
||||||
|
if let Some(dir) = path.parent() {
|
||||||
|
let _ = std::fs::create_dir_all(dir);
|
||||||
|
}
|
||||||
|
if let Ok(mut f) = std::fs::OpenOptions::new().create(true).append(true).open(&path) {
|
||||||
|
use std::io::Write;
|
||||||
|
let _ = writeln!(f, "{msg}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1004,18 +1032,24 @@ fn launch_as_admin(exe: &std::path::PathBuf, token: &str, port: u16) -> anyhow::
|
||||||
.args(["/Run", "/TN", HELPER_TASK_NAME])
|
.args(["/Run", "/TN", HELPER_TASK_NAME])
|
||||||
.output();
|
.output();
|
||||||
match run {
|
match run {
|
||||||
Ok(o) if o.status.success() => return Ok(()),
|
Ok(o) if o.status.success() => {
|
||||||
Ok(o) => eprintln!(
|
diag_log("run: schtasks /Run accepted — no consent prompt");
|
||||||
"[OSTP] schtasks /Run failed: {}",
|
return Ok(());
|
||||||
|
}
|
||||||
|
Ok(o) => diag_log(&format!(
|
||||||
|
"run: schtasks /Run failed ({:?}): {} {}",
|
||||||
|
o.status.code(),
|
||||||
|
String::from_utf8_lossy(&o.stdout).trim(),
|
||||||
String::from_utf8_lossy(&o.stderr).trim()
|
String::from_utf8_lossy(&o.stderr).trim()
|
||||||
),
|
)),
|
||||||
Err(e) => eprintln!("[OSTP] schtasks /Run could not start: {e}"),
|
Err(e) => diag_log(&format!("run: schtasks /Run could not start: {e}")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Falling through: remove the file so a stale token is not left behind.
|
// Falling through: remove the file so a stale token is not left behind.
|
||||||
let _ = std::fs::remove_file(&args_file);
|
let _ = std::fs::remove_file(&args_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
diag_log("falling back to a direct elevated launch — this is the consent prompt");
|
||||||
launch_as_admin_direct(exe, token, port)
|
launch_as_admin_direct(exe, token, port)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,29 @@
|
||||||
Pop $R0
|
Pop $R0
|
||||||
|
|
||||||
${If} $R0 == 0
|
${If} $R0 == 0
|
||||||
DetailPrint "Helper task registered; connecting will not ask for consent."
|
; Registering the task is not enough to make it usable. The principal above
|
||||||
|
; decides WHO THE TASK RUNS AS; the task's security descriptor decides who
|
||||||
|
; is allowed to START it, and they are not the same thing. A task created by
|
||||||
|
; an elevated installer defaults to a DACL granting execution to
|
||||||
|
; Administrators only, so the unprivileged GUI got
|
||||||
|
; schtasks /Run -> ERROR: Access is denied
|
||||||
|
; and fell back to prompting on every single connect. Running it by hand
|
||||||
|
; from an elevated console worked, which is what made this look for a while
|
||||||
|
; like the app was at fault.
|
||||||
|
;
|
||||||
|
; Register-ScheduledTask cannot set a descriptor, so this goes through the
|
||||||
|
; Task Scheduler COM object. GA for Administrators and SYSTEM, GR+GX —
|
||||||
|
; read and execute — for BUILTIN\Users (BU), which is what lets a normal
|
||||||
|
; user start it without being elevated.
|
||||||
|
DetailPrint "Granting users permission to start the task..."
|
||||||
|
nsExec::ExecToLog `powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command "$$svc = New-Object -ComObject Schedule.Service; $$svc.Connect(); $$t = $$svc.GetFolder('\').GetTask('OSTP TUN Helper'); $$t.SetSecurityDescriptor('D:(A;;GA;;;BA)(A;;GA;;;SY)(A;;GRGX;;;BU)', 0)"`
|
||||||
|
Pop $R1
|
||||||
|
${If} $R1 == 0
|
||||||
|
DetailPrint "Helper task registered; connecting will not ask for consent."
|
||||||
|
${Else}
|
||||||
|
DetailPrint "Task registered but its permissions could not be set (exit $R1)."
|
||||||
|
DetailPrint "Every connect will ask for consent."
|
||||||
|
${EndIf}
|
||||||
${Else}
|
${Else}
|
||||||
; Not fatal: the app still works, it just falls back to an elevated launch
|
; Not fatal: the app still works, it just falls back to an elevated launch
|
||||||
; that asks for consent on each connect.
|
; that asks for consent on each connect.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue