╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ serve_ssh_protocol.zig ───────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
1599 fn sendChannelOpenFailure( 1600 io: std.Io, 1601 allocator: std.mem.Allocator, 1602 sc_cipher: *Cipher, 1603 writer: *std.Io.Writer, 1604 open_packet: []const u8, 1605 reason: u32, 1606 description: []const u8, 1607 ) !void { 1608 // recover the sender_channel field from the CHANNEL_OPEN packet so we 1609 // address the failure to the correct id 1610 var r = std.Io.Reader.fixed(open_packet[1..]); 1611 const ch_type_len = try r.takeInt(u32, .big); 1612 try r.discardAll(ch_type_len); 1613 const remote_id = try r.takeInt(u32, .big); 1614 1615 var reply: std.ArrayList(u8) = .empty; 1616 defer reply.deinit(allocator); 1617 try reply.append(allocator, SSH_MSG_CHANNEL_OPEN_FAILURE); 1618 try writeU32(&reply, allocator, remote_id); 1619 try writeU32(&reply, allocator, reason); 1620 try writeStringField(&reply, allocator, description); 1621 try writeStringField(&reply, allocator, ""); // language tag 1622 try sc_cipher.writePacket(io, allocator, writer, reply.items); 1623 } 1624 1625 const RequestKind = enum { shell, exec }; 1626 1627 /// Returns the kind of session request if this triggered one, else null. 1628 fn handleChannelRequest( 1629 io: std.Io, 1630 allocator: std.mem.Allocator, 1631 sc_cipher: *Cipher, 1632 writer: *std.Io.Writer, 1633 ch: *Channel, 1634 packet: []const u8, 1635 pending_exec: *?[]u8, 1636 ) !?RequestKind { 1637 // byte SSH_MSG_CHANNEL_REQUEST 1638 // uint32 recipient_channel (== our local_id) 1639 // string request_type 1640 // boolean want_reply 1641 // …request-specific data 1642 var r = std.Io.Reader.fixed(packet[1..]); 1643 const recipient_channel = try r.takeInt(u32, .big); 1644 if (recipient_channel != ch.local_id) return error.UnknownChannel; 1645 const req_type = try takeStringField(allocator, &r, 64); 1646 defer allocator.free(req_type); 1647 const want_reply = (try r.takeByte()) != 0; 1648 1649 if (std.mem.eql(u8, req_type, "pty-req")) { 1650 // string TERM, uint32 width chars, uint32 height rows, uint32 width px, 1651 // uint32 height px, string modes 1652 const term = try takeStringField(allocator, &r, 64); 1653 defer allocator.free(term); 1654 const width = try r.takeInt(u32, .big); 1655 const height = try r.takeInt(u32, .big); 1656 ch.pty = .{ .width_cells = @intCast(@min(width, 0xFFFF)), .height_cells = @intCast(@min(height, 0xFFFF)) }; 1657 try replyChannelRequest(io, allocator, sc_cipher, writer, ch, want_reply, true); 1658 return null; 1659 } 1660 1661 if (std.mem.eql(u8, req_type, "env")) { 1662 // ignore environment variables silently (we don't pass them anywhere) 1663 try replyChannelRequest(io, allocator, sc_cipher, writer, ch, want_reply, true); 1664 return null; 1665 } 1666 1667 if (std.mem.eql(u8, req_type, "window-change")) { 1668 // uint32 width cells, uint32 height rows, uint32 width px, uint32 height px 1669 const width = try r.takeInt(u32, .big); 1670 const height = try r.takeInt(u32, .big); 1671 if (ch.pty != null) { 1672 ch.pty = .{ .width_cells = @intCast(@min(width, 0xFFFF)), .height_cells = @intCast(@min(height, 0xFFFF)) }; 1673 } 1674 // spec: window-change MUST NOT request a reply, but accept either 1675 try replyChannelRequest(io, allocator, sc_cipher, writer, ch, want_reply, true); 1676 return null; 1677 } 1678 1679 if (std.mem.eql(u8, req_type, "shell")) { 1680 try replyChannelRequest(io, allocator, sc_cipher, writer, ch, want_reply, true); 1681 return .shell; 1682 } 1683 1684 if (std.mem.eql(u8, req_type, "exec")) { 1685 // capture the command string for the handler to inspect 1686 const cmd = try takeStringField(allocator, &r, 4096); 1687 if (pending_exec.*) |old| allocator.free(old); 1688 pending_exec.* = cmd; 1689 try replyChannelRequest(io, allocator, sc_cipher, writer, ch, want_reply, true); 1690 return .exec; 1691 } 1692 1693 // unknown request kind — fail it 1694 try replyChannelRequest(io, allocator, sc_cipher, writer, ch, want_reply, false); 1695 return null; 1696 } 1697 1698 fn replyChannelRequest( 1699 io: std.Io, 1700 allocator: std.mem.Allocator, 1701 sc_cipher: *Cipher, 1702 writer: *std.Io.Writer, 1703 ch: *Channel, 1704 want_reply: bool, 1705 success: bool, 1706 ) !void { 1707 if (!want_reply) return; 1708 var reply: std.ArrayList(u8) = .empty; 1709 defer reply.deinit(allocator); 1710 try reply.append(allocator, if (success) SSH_MSG_CHANNEL_SUCCESS else SSH_MSG_CHANNEL_FAILURE); 1711 try writeU32(&reply, allocator, ch.remote_id); 1712 try sc_cipher.writePacket(io, allocator, writer, reply.items); 1713 } 1714 1715 fn handleWindowAdjust(ch: *Channel, packet: []const u8) !void { 1716 // byte SSH_MSG_CHANNEL_WINDOW_ADJUST 1717 // uint32 recipient_channel 1718 // uint32 bytes_to_add 1719 var r = std.Io.Reader.fixed(packet[1..]); 1720 const recipient_channel = try r.takeInt(u32, .big); 1721 if (recipient_channel != ch.local_id) return error.UnknownChannel; 1722 const add = try r.takeInt(u32, .big); 1723 ch.remote_window +|= add; 1724 } 1725 1726 fn discardChannelData( 1727 io: std.Io, 1728 allocator: std.mem.Allocator, 1729 sc_cipher: *Cipher, 1730 writer: *std.Io.Writer, 1731 ch: *Channel, 1732 packet: []const u8, 1733 extended: bool, 1734 ) !void { 1735 // byte SSH_MSG_CHANNEL_DATA/EXTENDED_DATA 1736 // uint32 recipient_channel 1737 // [if extended: uint32 type_code] 1738 // string data 1739 const payload = try parseChannelData(packet, extended, ch.local_id); 1740 const data_len: u32 = @intCast(payload.len); 1741 1742 if (data_len > ch.local_window) return error.WindowExceeded; 1743 ch.local_window -= data_len; 1744 try maybeRefillRecvWindow(io, allocator, sc_cipher, writer, ch); 1745 } 1746 1747 pub fn parseChannelData(packet: []const u8, extended: bool, expected_channel: u32) ![]const u8 { 1748 if (packet.len < 1) return error.EmptyPacket; 1749 var r = std.Io.Reader.fixed(packet[1..]); 1750 const recipient_channel = try r.takeInt(u32, .big); 1751 if (recipient_channel != expected_channel) return error.UnknownChannel; 1752 if (extended) { 1753 const data_type = try r.takeInt(u32, .big); 1754 if (data_type != SSH_EXTENDED_DATA_STDERR) return error.UnsupportedExtendedData; 1755 } 1756 const data_len = try r.takeInt(u32, .big); 1757 // catches both data_len > remaining (would otherwise be a bounds-check 1758 // panic on the slice) and trailing junk after the declared payload. 1759 if (data_len != r.buffered().len) return error.InvalidChannelData; 1760 return r.buffered(); 1761 } 1762 1763 fn parseChannelId(packet: []const u8, expected_channel: u32) !void { 1764 if (packet.len < 1) return error.EmptyPacket; 1765 var r = std.Io.Reader.fixed(packet[1..]); 1766 const recipient_channel = try r.takeInt(u32, .big); 1767 if (recipient_channel != expected_channel) return error.UnknownChannel; 1768 if (r.buffered().len != 0) return error.InvalidChannelMessage; 1769 } 1770 1771 /// Send a CHANNEL_WINDOW_ADJUST if our receive window has dipped below half 1772 /// of the initial value. Used both for the pre-session discard path and by 1773 /// SessionCtx after it consumes a CHANNEL_DATA payload. 1774 fn maybeRefillRecvWindow( 1775 io: std.Io, 1776 allocator: std.mem.Allocator, 1777 sc_cipher: *Cipher, 1778 writer: *std.Io.Writer, 1779 ch: *Channel, 1780 ) !void { 1781 if (ch.local_window >= initial_recv_window / 2) return; 1782 const add = initial_recv_window - ch.local_window; 1783 ch.local_window += add; 1784 var adj: std.ArrayList(u8) = .empty; 1785 defer adj.deinit(allocator); 1786 try adj.append(allocator, SSH_MSG_CHANNEL_WINDOW_ADJUST); 1787 try writeU32(&adj, allocator, ch.remote_id); 1788 try writeU32(&adj, allocator, add); 1789 try sc_cipher.writePacket(io, allocator, writer, adj.items); 1790 } 1791 1792 pub fn writeU32(buf: *std.ArrayList(u8), allocator: std.mem.Allocator, value: u32) !void { 1793 var bytes: [4]u8 = undefined; 1794 std.mem.writeInt(u32, &bytes, value, .big); 1795 try buf.appendSlice(allocator, &bytes); 1796 } 1797