Skip to main content

badterm_core/
telnet.rs

1//! Telnet protocol (RFC 854) with the option negotiation a BBS client needs:
2//! BINARY (RFC 856), ECHO (857), SGA (858), TERMINAL-TYPE (1091) and
3//! NAWS (1073).
4//!
5//! Negotiation follows the spirit of RFC 1143: we only answer requests that
6//! change an option's state, which prevents negotiation loops.
7
8pub const IAC: u8 = 255;
9pub const DONT: u8 = 254;
10pub const DO: u8 = 253;
11pub const WONT: u8 = 252;
12pub const WILL: u8 = 251;
13pub const SB: u8 = 250;
14pub const SE: u8 = 240;
15
16pub mod opt {
17    pub const BINARY: u8 = 0;
18    pub const ECHO: u8 = 1;
19    pub const SGA: u8 = 3;
20    pub const TTYPE: u8 = 24;
21    pub const NAWS: u8 = 31;
22}
23
24const TTYPE_IS: u8 = 0;
25const TTYPE_SEND: u8 = 1;
26const MAX_SB: usize = 1024;
27
28#[derive(Clone, Copy, Debug, PartialEq, Eq)]
29enum State {
30    Data,
31    Iac,
32    /// After IAC WILL/WONT/DO/DONT, waiting for the option byte.
33    Negotiate(u8),
34    Sb,
35    SbIac,
36    /// Saw CR in the data stream; a following NUL is dropped (NVT CR NUL).
37    Cr,
38}
39
40pub struct Telnet {
41    state: State,
42    /// Options enabled on our side (we WILL).
43    local: [bool; 256],
44    /// Options enabled on the server side (it WILL).
45    remote: [bool; 256],
46    sb: Vec<u8>,
47    pub term_type: String,
48    cols: u16,
49    rows: u16,
50}
51
52impl Telnet {
53    pub fn new(term_type: &str, cols: u16, rows: u16) -> Self {
54        Telnet {
55            state: State::Data,
56            local: [false; 256],
57            remote: [false; 256],
58            sb: Vec::new(),
59            term_type: term_type.to_string(),
60            cols,
61            rows,
62        }
63    }
64
65    /// Whether the server has agreed to echo our input.
66    pub fn remote_echo(&self) -> bool {
67        self.remote[opt::ECHO as usize]
68    }
69
70    /// Process bytes from the network. Application data is appended to
71    /// `data`; protocol replies to send back are appended to `reply`.
72    pub fn receive(&mut self, input: &[u8], data: &mut Vec<u8>, reply: &mut Vec<u8>) {
73        for &b in input {
74            self.state = match self.state {
75                State::Data | State::Cr => {
76                    let after_cr = self.state == State::Cr;
77                    match b {
78                        IAC => State::Iac,
79                        0 if after_cr => State::Data,
80                        b'\r' => {
81                            data.push(b);
82                            State::Cr
83                        }
84                        _ => {
85                            data.push(b);
86                            State::Data
87                        }
88                    }
89                }
90                State::Iac => match b {
91                    IAC => {
92                        data.push(IAC);
93                        State::Data
94                    }
95                    WILL | WONT | DO | DONT => State::Negotiate(b),
96                    SB => {
97                        self.sb.clear();
98                        State::Sb
99                    }
100                    // NOP, GA, AYT and friends carry no data for us.
101                    _ => State::Data,
102                },
103                State::Negotiate(cmd) => {
104                    self.negotiate(cmd, b, reply);
105                    State::Data
106                }
107                State::Sb => {
108                    if b == IAC {
109                        State::SbIac
110                    } else {
111                        if self.sb.len() < MAX_SB {
112                            self.sb.push(b);
113                        }
114                        State::Sb
115                    }
116                }
117                State::SbIac => match b {
118                    SE => {
119                        self.subnegotiation(reply);
120                        State::Data
121                    }
122                    IAC => {
123                        if self.sb.len() < MAX_SB {
124                            self.sb.push(IAC);
125                        }
126                        State::Sb
127                    }
128                    _ => State::Sb,
129                },
130            };
131        }
132    }
133
134    fn supports_local(o: u8) -> bool {
135        matches!(o, opt::BINARY | opt::SGA | opt::TTYPE | opt::NAWS)
136    }
137
138    fn supports_remote(o: u8) -> bool {
139        matches!(o, opt::BINARY | opt::SGA | opt::ECHO)
140    }
141
142    fn negotiate(&mut self, cmd: u8, o: u8, reply: &mut Vec<u8>) {
143        let i = o as usize;
144        match cmd {
145            WILL => {
146                if !Self::supports_remote(o) {
147                    reply.extend_from_slice(&[IAC, DONT, o]);
148                } else if !self.remote[i] {
149                    self.remote[i] = true;
150                    reply.extend_from_slice(&[IAC, DO, o]);
151                }
152            }
153            WONT if self.remote[i] => {
154                self.remote[i] = false;
155                reply.extend_from_slice(&[IAC, DONT, o]);
156            }
157            DO => {
158                if !Self::supports_local(o) {
159                    reply.extend_from_slice(&[IAC, WONT, o]);
160                } else if !self.local[i] {
161                    self.local[i] = true;
162                    reply.extend_from_slice(&[IAC, WILL, o]);
163                    if o == opt::NAWS {
164                        self.naws(reply);
165                    }
166                } else if o == opt::NAWS {
167                    // Some servers re-ask; resending the size is harmless.
168                    self.naws(reply);
169                }
170            }
171            DONT if self.local[i] => {
172                self.local[i] = false;
173                reply.extend_from_slice(&[IAC, WONT, o]);
174            }
175            _ => {}
176        }
177    }
178
179    fn subnegotiation(&mut self, reply: &mut Vec<u8>) {
180        if self.sb.first() == Some(&opt::TTYPE)
181            && self.sb.get(1) == Some(&TTYPE_SEND)
182            && self.local[opt::TTYPE as usize]
183        {
184            reply.extend_from_slice(&[IAC, SB, opt::TTYPE, TTYPE_IS]);
185            push_escaped(reply, self.term_type.as_bytes());
186            reply.extend_from_slice(&[IAC, SE]);
187        }
188    }
189
190    fn naws(&self, reply: &mut Vec<u8>) {
191        reply.extend_from_slice(&[IAC, SB, opt::NAWS]);
192        push_escaped(reply, &self.cols.to_be_bytes());
193        push_escaped(reply, &self.rows.to_be_bytes());
194        reply.extend_from_slice(&[IAC, SE]);
195    }
196
197    /// Update the window size, returning a NAWS report if the option is active.
198    pub fn set_window_size(&mut self, cols: u16, rows: u16) -> Vec<u8> {
199        self.cols = cols;
200        self.rows = rows;
201        let mut out = Vec::new();
202        if self.local[opt::NAWS as usize] {
203            self.naws(&mut out);
204        }
205        out
206    }
207
208    /// Encode application data for the wire: IAC is doubled and, outside of
209    /// binary mode, a bare CR becomes CR NUL as the NVT requires.
210    pub fn encode(&self, data: &[u8]) -> Vec<u8> {
211        let binary = self.local[opt::BINARY as usize];
212        let mut out = Vec::with_capacity(data.len() + 4);
213        for (i, &b) in data.iter().enumerate() {
214            out.push(b);
215            if b == IAC {
216                out.push(IAC);
217            } else if b == b'\r' && !binary && data.get(i + 1) != Some(&b'\n') {
218                out.push(0);
219            }
220        }
221        out
222    }
223}
224
225fn push_escaped(out: &mut Vec<u8>, bytes: &[u8]) {
226    for &b in bytes {
227        out.push(b);
228        if b == IAC {
229            out.push(IAC);
230        }
231    }
232}
233
234#[cfg(test)]
235mod tests {
236    use super::*;
237
238    fn run(t: &mut Telnet, input: &[u8]) -> (Vec<u8>, Vec<u8>) {
239        let (mut d, mut r) = (Vec::new(), Vec::new());
240        t.receive(input, &mut d, &mut r);
241        (d, r)
242    }
243
244    #[test]
245    fn data_and_escaped_iac() {
246        let mut t = Telnet::new("ANSI", 80, 25);
247        let (d, r) = run(&mut t, &[b'a', IAC, IAC, b'b']);
248        assert_eq!(d, [b'a', 255, b'b']);
249        assert!(r.is_empty());
250    }
251
252    #[test]
253    fn negotiation_is_answered_once() {
254        let mut t = Telnet::new("ANSI", 80, 25);
255        let (_, r) = run(&mut t, &[IAC, WILL, opt::ECHO, IAC, WILL, opt::ECHO]);
256        assert_eq!(r, [IAC, DO, opt::ECHO]);
257        assert!(t.remote_echo());
258        let (_, r) = run(&mut t, &[IAC, DO, 42]);
259        assert_eq!(r, [IAC, WONT, 42]);
260    }
261
262    #[test]
263    fn naws_and_ttype() {
264        let mut t = Telnet::new("ANSI", 80, 25);
265        let (_, r) = run(&mut t, &[IAC, DO, opt::NAWS]);
266        assert_eq!(r, [IAC, WILL, opt::NAWS, IAC, SB, opt::NAWS, 0, 80, 0, 25, IAC, SE]);
267        let (_, r) = run(&mut t, &[IAC, DO, opt::TTYPE, IAC, SB, opt::TTYPE, 1, IAC, SE]);
268        let mut want = vec![IAC, WILL, opt::TTYPE, IAC, SB, opt::TTYPE, 0];
269        want.extend_from_slice(b"ANSI");
270        want.extend_from_slice(&[IAC, SE]);
271        assert_eq!(r, want);
272    }
273
274    #[test]
275    fn split_across_reads() {
276        let mut t = Telnet::new("ANSI", 80, 25);
277        let (d, _) = run(&mut t, &[b'x', IAC]);
278        assert_eq!(d, b"x");
279        let (d, r) = run(&mut t, &[WILL, opt::SGA, b'y']);
280        assert_eq!(d, b"y");
281        assert_eq!(r, [IAC, DO, opt::SGA]);
282    }
283
284    #[test]
285    fn encode_cr_and_iac() {
286        let t = Telnet::new("ANSI", 80, 25);
287        assert_eq!(t.encode(&[b'\r', 255]), [b'\r', 0, 255, 255]);
288        assert_eq!(t.encode(b"\r\n"), b"\r\n");
289    }
290
291    #[test]
292    fn cr_nul_from_server_is_stripped() {
293        let mut t = Telnet::new("ANSI", 80, 25);
294        let (d, _) = run(&mut t, b"a\r\0b");
295        assert_eq!(d, b"a\rb");
296    }
297}