╭ changed ─────────────────╮ │src/serve_ssh_protocol.zig│ ╰──────────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭────────────────────────────────────────────────────────────────────────────────────╮ │53 const max_exit_drain_packets: u32 = 32; │ │54 │ │55 // SSH message type bytes (RFC 4250 §4.1) │ │56 -const SSH_MSG_DISCONNECT: u8 = 1; │ │57 -const SSH_MSG_SERVICE_REQUEST: u8 = 5; │ │58 -const SSH_MSG_SERVICE_ACCEPT: u8 = 6; │ │59 -const SSH_MSG_KEXINIT: u8 = 20; │ │60 -const SSH_MSG_NEWKEYS: u8 = 21; │ │61 -const SSH_MSG_KEX_ECDH_INIT: u8 = 30; │ │62 -const SSH_MSG_KEX_ECDH_REPLY: u8 = 31; │ │63 -const SSH_MSG_USERAUTH_REQUEST: u8 = 50; │ │64 -const SSH_MSG_USERAUTH_FAILURE: u8 = 51; │ │65 -const SSH_MSG_USERAUTH_SUCCESS: u8 = 52; │ │66 -const SSH_MSG_USERAUTH_PK_OK: u8 = 60; // method-specific name for publickey │ │67 -const SSH_MSG_GLOBAL_REQUEST: u8 = 80; │ │68 -const SSH_MSG_REQUEST_SUCCESS: u8 = 81; │ │69 -const SSH_MSG_REQUEST_FAILURE: u8 = 82; │ │70 -const SSH_MSG_CHANNEL_OPEN: u8 = 90; │ │71 -const SSH_MSG_CHANNEL_OPEN_CONFIRMATION: u8 = 91; │ │72 -const SSH_MSG_CHANNEL_OPEN_FAILURE: u8 = 92; │ │73 -const SSH_MSG_CHANNEL_WINDOW_ADJUST: u8 = 93; │ │74 -const SSH_MSG_CHANNEL_DATA: u8 = 94; │ │75 -const SSH_MSG_CHANNEL_EXTENDED_DATA: u8 = 95; │ │76 -const SSH_MSG_CHANNEL_EOF: u8 = 96; │ │77 -const SSH_MSG_CHANNEL_CLOSE: u8 = 97; │ │78 -const SSH_MSG_CHANNEL_REQUEST: u8 = 98; │ │79 -const SSH_MSG_CHANNEL_SUCCESS: u8 = 99; │ │80 -const SSH_MSG_CHANNEL_FAILURE: u8 = 100; │ │56 +pub const SSH_MSG_DISCONNECT: u8 = 1; │ │57 +pub const SSH_MSG_SERVICE_REQUEST: u8 = 5; │ │58 +pub const SSH_MSG_SERVICE_ACCEPT: u8 = 6; │ │59 +pub const SSH_MSG_KEXINIT: u8 = 20; │ │60 +pub const SSH_MSG_NEWKEYS: u8 = 21; │ │61 +pub const SSH_MSG_KEX_ECDH_INIT: u8 = 30; │ │62 +pub const SSH_MSG_KEX_ECDH_REPLY: u8 = 31; │ │63 +pub const SSH_MSG_USERAUTH_REQUEST: u8 = 50; │ │64 +pub const SSH_MSG_USERAUTH_FAILURE: u8 = 51; │ │65 +pub const SSH_MSG_USERAUTH_SUCCESS: u8 = 52; │ │66 +pub const SSH_MSG_USERAUTH_PK_OK: u8 = 60; // method-specific name for publickey│ │67 +pub const SSH_MSG_GLOBAL_REQUEST: u8 = 80; │ │68 +pub const SSH_MSG_REQUEST_SUCCESS: u8 = 81; │ │69 +pub const SSH_MSG_REQUEST_FAILURE: u8 = 82; │ │70 +pub const SSH_MSG_CHANNEL_OPEN: u8 = 90; │ │71 +pub const SSH_MSG_CHANNEL_OPEN_CONFIRMATION: u8 = 91; │ │72 +pub const SSH_MSG_CHANNEL_OPEN_FAILURE: u8 = 92; │ │73 +pub const SSH_MSG_CHANNEL_WINDOW_ADJUST: u8 = 93; │ │74 +pub const SSH_MSG_CHANNEL_DATA: u8 = 94; │ │75 +pub const SSH_MSG_CHANNEL_EXTENDED_DATA: u8 = 95; │ │76 +pub const SSH_MSG_CHANNEL_EOF: u8 = 96; │ │77 +pub const SSH_MSG_CHANNEL_CLOSE: u8 = 97; │ │78 +pub const SSH_MSG_CHANNEL_REQUEST: u8 = 98; │ │79 +pub const SSH_MSG_CHANNEL_SUCCESS: u8 = 99; │ │80 +pub const SSH_MSG_CHANNEL_FAILURE: u8 = 100; │ │81 │ │82 -const SSH_DISCONNECT_PROTOCOL_ERROR: u32 = 2; │ │82 +pub const SSH_DISCONNECT_PROTOCOL_ERROR: u32 = 2; │ │83 │ │84 // SSH_OPEN_* reason codes for CHANNEL_OPEN_FAILURE │ │85 -const SSH_OPEN_RESOURCE_SHORTAGE: u32 = 4; │ │86 -const SSH_OPEN_UNKNOWN_CHANNEL_TYPE: u32 = 3; │ │85 +pub const SSH_OPEN_RESOURCE_SHORTAGE: u32 = 4; │ │86 +pub const SSH_OPEN_UNKNOWN_CHANNEL_TYPE: u32 = 3; │ │87 │ │88 // data type code for CHANNEL_EXTENDED_DATA (stderr) │ │89 -const SSH_EXTENDED_DATA_STDERR: u32 = 1; │ │89 +pub const SSH_EXTENDED_DATA_STDERR: u32 = 1; │ │90 │ │91 // initial flow-control window we advertise. typical openssh setting; large │ │92 // enough that small interactive sessions never need a WINDOW_ADJUST. │ ╰────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │133 │ │134 /// SSH wire-format ed25519 public key blob (used as K_S in the hash and │ │135 /// returned in KEX_ECDH_REPLY): string "ssh-ed25519" || string pubkey. │ │136 - fn appendPublicBlob(self: HostKey, buf: *std.ArrayList(u8), allocator: std.mem.Allocator) !void { │ │136 + pub fn appendPublicBlob(self: HostKey, buf: *std.ArrayList(u8), allocator: std.mem.Allocator) !void {│ │137 try writeStringField(buf, allocator, "ssh-ed25519"); │ │138 try writeStringField(buf, allocator, &self.keypair.public_key.bytes); │ │139 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────╮ │541 /// `handler` must be a pointer to a struct with a method: │ │542 /// pub fn handleSession(self, sess: *SessionCtx, request: Request) anyerror!void│ │543 /// invoked once the channel is open and a shell/exec request has arrived. │ │544 +/// │ │545 +/// `reader` and `writer` are the bidirectional byte stream │ │546 pub fn handleConnection( │ │547 io: std.Io, │ │548 allocator: std.mem.Allocator, │ ╰────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────╮ │547 - stream: std.Io.net.Stream,│ │549 + reader: *std.Io.Reader, │ │550 + writer: *std.Io.Writer, │ │551 host_key: *const HostKey, │ │552 handler: anytype, │ │553 ) !void { │ ╰───────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────╮ │551 - var recv_buf: [4096]u8 = undefined; │ │552 - var send_buf: [4096]u8 = undefined; │ │553 - var stream_reader = stream.reader(io, &recv_buf); │ │554 - var stream_writer = stream.writer(io, &send_buf); │ │555 - const reader = &stream_reader.interface; │ │556 - const writer = &stream_writer.interface; │ │557 - │ │554 const client_version = try exchangeVersions(allocator, reader, writer);│ │555 defer allocator.free(client_version); │ │556 │ ╰────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │594 // binary packet codec, unencrypted form (RFC 4253 §6) │ │595 // --------------------------------------------------------------------------- │ │596 │ │601 -fn writePlainPacket(io: std.Io, writer: *std.Io.Writer, payload: []const u8) !void { │ │597 +pub fn writePlainPacket(io: std.Io, writer: *std.Io.Writer, payload: []const u8) !void { │ │598 const block: usize = 8; │ │599 const initial_pad = block - ((5 + payload.len) % block); │ │600 const padding_len: u8 = @intCast(if (initial_pad < 4) initial_pad + block else initial_pad);│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────╮ │610 try writer.flush(); │ │611 } │ │612 │ │617 -fn readPlainPacket(allocator: std.mem.Allocator, reader: *std.Io.Reader) ![]u8 { │ │613 +pub fn readPlainPacket(allocator: std.mem.Allocator, reader: *std.Io.Reader) ![]u8 { │ │614 const packet_len = try reader.takeInt(u32, .big); │ │615 if (packet_len < 8 or packet_len > max_packet_len) return error.InvalidPacketLength;│ │616 const padding_len = try reader.takeByte(); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │627 // SSH field encoders / decoders (RFC 4251 §5) │ │628 // --------------------------------------------------------------------------- │ │629 │ │634 -fn writeStringField(buf: *std.ArrayList(u8), allocator: std.mem.Allocator, bytes: []const u8) !void { │ │630 +pub fn writeStringField(buf: *std.ArrayList(u8), allocator: std.mem.Allocator, bytes: []const u8) !void {│ │631 var len_bytes: [4]u8 = undefined; │ │632 std.mem.writeInt(u32, &len_bytes, @intCast(bytes.len), .big); │ │633 try buf.appendSlice(allocator, &len_bytes); │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │634 try buf.appendSlice(allocator, bytes); │ │635 } │ │636 │ │641 -fn writeNameList(buf: *std.ArrayList(u8), allocator: std.mem.Allocator, names: []const []const u8) !void { │ │637 +pub fn writeNameList(buf: *std.ArrayList(u8), allocator: std.mem.Allocator, names: []const []const u8) !void {│ │638 var total: usize = 0; │ │639 for (names, 0..) |name, i| { │ │640 if (i > 0) total += 1; // comma separator │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │653 /// our use (X25519 shared secret) the value is always non-negative, so we │ │654 /// strip leading zero bytes, then prepend a single 0x00 if the high bit of │ │655 /// the most-significant byte is set (to keep it parsed as positive). │ │660 -fn writeMpint(buf: *std.ArrayList(u8), allocator: std.mem.Allocator, bytes: []const u8) !void { │ │656 +pub fn writeMpint(buf: *std.ArrayList(u8), allocator: std.mem.Allocator, bytes: []const u8) !void {│ │657 var start: usize = 0; │ │658 while (start < bytes.len and bytes[start] == 0) start += 1; │ │659 const trimmed = bytes[start..]; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯