diff --git a/src/client.zig b/src/client.zig index ac78458..ace263b 100644 --- a/src/client.zig +++ b/src/client.zig @@ -5,6 +5,8 @@ const fs = std.fs; const os = std.os; const print = std.debug.print; const telnet = @import("telnet.zig"); +const Command = telnet.Command; +const Option = telnet.Option; const State = enum { normal, @@ -12,11 +14,17 @@ const State = enum { negotiating, }; +const StateInfo = union(State) { + normal: void, + iac: void, + negotiating: telnet.Command, +}; + pub const TelnetClient = struct { stream: net.Stream, reader: net.Stream.Reader, writer: net.Stream.Writer, - state: State, + state: StateInfo, pub fn init(stream: net.Stream) TelnetClient { return TelnetClient{ @@ -33,7 +41,7 @@ pub const TelnetClient = struct { switch (self.state) { .normal => { if (byte == telnet.IAC_BYTE) { - print("IAC ", .{}); + print("Server ({s}): IAC ", .{@tagName(self.state)}); self.state = .iac; } else { print("{c}", .{byte}); @@ -88,27 +96,85 @@ pub const TelnetClient = struct { }, .will => { print("WILL ", .{}); - self.state = .negotiating; + self.state = StateInfo{ .negotiating = .will }; }, .wont => { print("WONT ", .{}); - self.state = .negotiating; + self.state = StateInfo{ .negotiating = .wont }; }, .do => { print("DO ", .{}); - self.state = .negotiating; + self.state = StateInfo{ .negotiating = .do }; }, .dont => { print("DONT ", .{}); - self.state = .negotiating; + self.state = StateInfo{ .negotiating = .dont }; }, } }, - .negotiating => { - var opt: telnet.Option = @enumFromInt(byte); + .negotiating => |command| { + const opt: telnet.Option = @enumFromInt(byte); print("{s}\n", .{@tagName(opt)}); + + switch (opt) { + .echo => { + switch (command) { + .will => { + try self.send(.do, .echo); + }, + .wont => { + try self.send(.dont, .echo); + }, + else => {}, + } + }, + .suppressGoAhead => { + switch (command) { + .will => { + try self.send(.do, .suppressGoAhead); + }, + .wont => { + try self.send(.dont, .suppressGoAhead); + }, + else => {}, + } + }, + .negotiateAboutWindowSize => { + // https://datatracker.ietf.org/doc/html/rfc1073 + switch (command) { + .do => { + try self.send(.will, .negotiateAboutWindowSize); + try self.writer.writeAll(&[_]u8{ telnet.IAC_BYTE, telnet.SB_BYTE, @intFromEnum(Option.negotiateAboutWindowSize), 0, 80, 0, 24, telnet.IAC_BYTE, telnet.SE_BYTE }); + }, + .dont => { + try self.send(.wont, .negotiateAboutWindowSize); + }, + else => {}, + } + }, + .terminalType => { + // https://datatracker.ietf.org/doc/html/rfc1091 + switch (command) { + .do => { + try self.send(.wont, .terminalType); + try self.writer.writeAll(&[_]u8{ telnet.IAC_BYTE, telnet.SB_BYTE, @intFromEnum(Option.terminalType), 0, 0, 0, 0, 0, 0, 0, 0, 0, telnet.IAC_BYTE, telnet.SE_BYTE }); + }, + .dont => { + try self.send(.wont, .terminalType); + }, + else => {}, + } + }, + else => {}, + } + self.state = .normal; }, } } + + fn send(self: *TelnetClient, command: Command, option: Option) anyerror!void { + print("Client ({s}): IAC {s} {s}\n", .{ @tagName(self.state), @tagName(command), @tagName(option) }); + try self.writer.writeAll(&telnet.instruction(command, option)); + } }; diff --git a/src/telnet.zig b/src/telnet.zig index 3608935..5cb6938 100644 --- a/src/telnet.zig +++ b/src/telnet.zig @@ -1,6 +1,8 @@ pub const DEFAULT_PORT: u16 = 23; pub const IAC_BYTE: u8 = 255; +pub const SB_BYTE: u8 = 250; +pub const SE_BYTE: u8 = 240; pub const Command = enum(u8) { se = 240, // End of subnegotiation parameters @@ -24,7 +26,7 @@ pub const Option = enum(u8) { transmitBinary = 0, // Binary Transmission (RFC 856) echo = 1, // Echo (RFC 857) reconnection = 2, // Reconnection (NIC 15391 of 1973) - suppressGoAhead = 3, // Suppress Go Ahead (RFC 858) + suppressGoAhead = 3, // Suppress Go Ahead (RFC 858): no "go ahead" signal will be sent (required for half-duplex transmissions) -> full-duplex approxMessageSizeNegotiation = 4, // Approx Message Size Negotiation (NIC 15393 of 1973) status = 5, // Status (RFC 859) timingMark = 6, // Timing Mark (RFC 860) @@ -78,3 +80,7 @@ pub const Option = enum(u8) { unassigned141To254 = 141, // Unassigned (141-254) extendedOptionsList = 255, // Extended-Options-List (RFC 861) }; + +pub fn instruction(command: Command, option: Option) [3]u8 { + return [3]u8{ IAC_BYTE, @intFromEnum(command), @intFromEnum(option) }; +}