e99d4a1f
refactor: a prompt is attributed to the ssh mux spawned, however many forks sit between
a73x 2026-08-30 10:59
Commit message
src/client/askpass.zig
| Old | New | ||
|---|---|---|---|
| @@ -93,10 +93,10 @@ pub const Prompt = struct { | |||
| 93 | text: [prompt_max]u8 = undefined, | 93 | text: [prompt_max]u8 = undefined, |
| 94 | text_len: usize = 0, | 94 | text_len: usize = 0, |
| 95 | kind: Kind = .secret, | 95 | kind: Kind = .secret, |
| 96 | /// pid of the ssh whose helper connected: `SO_PEERCRED` names the | 96 | /// The dial this prompt belongs to: the ancestor of the connecting |
| 97 | /// helper, and the helper's parent IS ssh, which execs it directly. | 97 | /// helper that is THIS process's child, which is the ssh a wall dial |
| 98 | /// 0 when the walk failed — attribution is a convenience, never a | 98 | /// spawned (`dialOwner`). 0 when the walk found none — attribution is |
| 99 | /// condition for answering. | 99 | /// a convenience, never a condition for answering. |
| 100 | ssh_pid: std.posix.pid_t = 0, | 100 | ssh_pid: std.posix.pid_t = 0, |
| 101 | 101 | ||
| 102 | pub fn slice(self: *const Prompt) []const u8 { | 102 | pub fn slice(self: *const Prompt) []const u8 { |
| @@ -505,12 +505,14 @@ fn dialOwner( | |||
| 505 | parent: *const fn (std.posix.pid_t) std.posix.pid_t, | 505 | parent: *const fn (std.posix.pid_t) std.posix.pid_t, |
| 506 | ) std.posix.pid_t { | 506 | ) std.posix.pid_t { |
| 507 | // Not simply the helper's parent, which is what ssh execing its helper | 507 | // Not simply the helper's parent, which is what ssh execing its helper |
| 508 | // directly makes it. A shell can sit in between — a test's stand-in | 508 | // directly makes it. More can sit in between: under ProxyJump (and its |
| 509 | // does, and so does a real ssh whose ProxyCommand is one — and an | 509 | // `ProxyCommand ssh -W` spelling) the INNER ssh inherits `SSH_ASKPASS` |
| 510 | // attribution that breaks on one extra fork returns 0 exactly when a | 510 | // and prompts for the jump host through it, so the chain is helper → |
| 511 | // wall needs a name. What is INVARIANT is the other end: the ssh a | 511 | // inner ssh → outer ssh → us; a test's stand-in adds a shell the same |
| 512 | // dial spawned is a child of this process, and nothing else on the | 512 | // way. An attribution that breaks on one extra fork returns 0 exactly |
| 513 | // path is. | 513 | // when a wall needs a name. What is INVARIANT is the other end: the |
| 514 | // ssh a dial spawned is a child of this process, and nothing else on | ||
| 515 | // the path is. | ||
| 514 | var at = peer; | 516 | var at = peer; |
| 515 | var steps: usize = 0; | 517 | var steps: usize = 0; |
| 516 | while (at > 0 and steps < ancestor_max) : (steps += 1) { | 518 | while (at > 0 and steps < ancestor_max) : (steps += 1) { |
| @@ -729,11 +731,12 @@ const FakeTree = struct { | |||
| 729 | }; | 731 | }; |
| 730 | 732 | ||
| 731 | test "askpass: a helper two shells below the ssh we spawned is still that ssh's" { | 733 | test "askpass: a helper two shells below the ssh we spawned is still that ssh's" { |
| 732 | // ssh execs its helper directly, so production is one step. A stand-in | 734 | // ssh execs its helper directly, so the plain case is one step. A |
| 733 | // — and a real ssh whose ProxyCommand is a shell — puts more in | 735 | // ProxyJump is two — the inner ssh inherits `SSH_ASKPASS` and prompts |
| 734 | // between, and an attribution that breaks on one extra fork answers 0 | 736 | // for the jump host through it — and a test's stand-in adds a shell. |
| 735 | // exactly where a wall needs a name. The invariant is the far end: the | 737 | // An attribution that breaks on one extra fork answers 0 exactly where |
| 736 | // ssh a dial spawned is OUR child, and nothing else on the path is. | 738 | // a wall needs a name. The invariant is the far end: the ssh a dial |
| 739 | // spawned is OUR child, and nothing else on the path is. | ||
| 737 | try std.testing.expectEqual(@as(std.posix.pid_t, 97), dialOwner(100, 7, FakeTree.parent)); | 740 | try std.testing.expectEqual(@as(std.posix.pid_t, 97), dialOwner(100, 7, FakeTree.parent)); |
| 738 | // The direct case, unchanged. | 741 | // The direct case, unchanged. |
| 739 | try std.testing.expectEqual(@as(std.posix.pid_t, 97), dialOwner(97, 7, FakeTree.parent)); | 742 | try std.testing.expectEqual(@as(std.posix.pid_t, 97), dialOwner(97, 7, FakeTree.parent)); |
src/client/webhub.zig
| Old | New | ||
|---|---|---|---|
| @@ -8,6 +8,10 @@ | |||
| 8 | //! Localhost only, by construction: the hub binds 127.0.0.1 and there is | 8 | //! Localhost only, by construction: the hub binds 127.0.0.1 and there is |
| 9 | //! no flag to change that in v1 — remote viewing is `ssh -L`, | 9 | //! no flag to change that in v1 — remote viewing is `ssh -L`, |
| 10 | //! authenticated by ssh like everything else in this project. | 10 | //! authenticated by ssh like everything else in this project. |
| 11 | //! | ||
| 12 | //! No popup, so no prompts: every target here keeps `ask_sock` null and a | ||
| 13 | //! birth on a host that wants a password fails with ssh's own reason on | ||
| 14 | //! `/tiles`. A browser is not somewhere to type one. | ||
| 11 | 15 | ||
| 12 | const std = @import("std"); | 16 | const std = @import("std"); |
| 13 | const proto = @import("protocol"); | 17 | const proto = @import("protocol"); |
src/tui/wallview.zig
| Old | New | ||
|---|---|---|---|
| @@ -1694,8 +1694,10 @@ pub fn run(alloc: std.mem.Allocator, host_specs: []const HostSpec, entry: Entry) | |||
| 1694 | } else |_| {} | 1694 | } else |_| {} |
| 1695 | } | 1695 | } |
| 1696 | } | 1696 | } |
| 1697 | // Before the raw-mode teardown below, so the socket is gone whichever | 1697 | // The ERROR returns between here and the keys loop, and those only: |
| 1698 | // way this function leaves. | 1698 | // this function ends in `std.posix.exit`, which reaches no defer at |
| 1699 | // all. The normal way out is `retire` at the tail, and the two are not | ||
| 1700 | // interchangeable — see there. | ||
| 1699 | defer if (shared.prompts) |l| l.stop(); | 1701 | defer if (shared.prompts) |l| l.stop(); |
| 1700 | 1702 | ||
| 1701 | // Allocated at CAPACITY, not at length: the pump threads are detached | 1703 | // Allocated at CAPACITY, not at length: the pump threads are detached |