Skip to main content

telnetd/
main.rs

1#![no_std]
2#![no_main]
3#![feature(alloc_error_handler)]
4
5extern crate alloc;
6
7use core::{alloc::Layout, fmt::Write, panic::PanicInfo};
8use strat9_syscall::{call, data::TimeSpec, number};
9
10alloc_freelist::define_freelist_allocator!(pub struct BumpAllocator; heap_size = 128 * 1024;);
11
12#[global_allocator]
13static GLOBAL_ALLOCATOR: BumpAllocator = BumpAllocator;
14
15#[alloc_error_handler]
16/// Implements alloc error.
17fn alloc_error(_layout: Layout) -> ! {
18    log("[telnetd] OOM\n");
19    call::exit(12)
20}
21
22#[panic_handler]
23/// Implements panic.
24fn panic(info: &PanicInfo) -> ! {
25    log("[telnetd] PANiK: ");
26    let mut buf = [0u8; 256];
27    let n = {
28        let mut w = BufWriter {
29            buf: &mut buf,
30            pos: 0,
31        };
32        let _ = write!(w, "{}", info.message());
33        w.pos
34    };
35    if let Ok(s) = core::str::from_utf8(&buf[..n]) {
36        log(s);
37    }
38    log("\n");
39    call::exit(255)
40}
41
42struct BufWriter<'a> {
43    buf: &'a mut [u8],
44    pos: usize,
45}
46
47impl core::fmt::Write for BufWriter<'_> {
48    /// Writes str.
49    fn write_str(&mut self, s: &str) -> core::fmt::Result {
50        let bytes = s.as_bytes();
51        let avail = self.buf.len().saturating_sub(self.pos);
52        let n = bytes.len().min(avail);
53        self.buf[self.pos..self.pos + n].copy_from_slice(&bytes[..n]);
54        self.pos += n;
55        Ok(())
56    }
57}
58
59/// Implements log.
60fn log(msg: &str) {
61    let _ = call::debug_log(msg.as_bytes());
62}
63
64/// Implements sleep ms.
65fn sleep_ms(ms: u64) {
66    let req = TimeSpec {
67        tv_sec: (ms / 1000) as i64,
68        tv_nsec: ((ms % 1000) * 1_000_000) as i64,
69    };
70    let _ = unsafe {
71        strat9_syscall::syscall2(number::SYS_NANOSLEEP, &req as *const TimeSpec as usize, 0)
72    };
73}
74
75/// Writes all.
76fn write_all(fd: usize, data: &[u8]) -> bool {
77    let mut off = 0usize;
78    while off < data.len() {
79        match call::write(fd, &data[off..]) {
80            Ok(0) => return false,
81            Ok(n) => off += n,
82            Err(e) => {
83                if e.to_errno() == 11 {
84                    sleep_ms(10);
85                    continue;
86                }
87                return false;
88            }
89        }
90    }
91    true
92}
93
94/// Reads text file.
95fn read_text_file(path: &str, out: &mut [u8]) -> usize {
96    let fd = match call::openat(0, path, 0x0, 0) {
97        Ok(fd) => fd as usize,
98        Err(_) => return 0,
99    };
100    let n = call::read(fd, out).unwrap_or(0);
101    let _ = call::close(fd);
102    n
103}
104
105/// Opens listener.
106fn open_listener() -> usize {
107    loop {
108        match call::openat(0, "/net/tcp/listen/23", 0x2, 0) {
109            Ok(fd) => return fd as usize,
110            Err(_) => sleep_ms(200),
111        }
112    }
113}
114
115enum LineAction {
116    Continue,
117    Disconnect,
118}
119
120struct TelnetSession {
121    connected: bool,
122    line: [u8; 256],
123    line_len: usize,
124    iac_skip: u8,
125}
126
127impl TelnetSession {
128    /// Creates a new instance.
129    const fn new() -> Self {
130        Self {
131            connected: false,
132            line: [0u8; 256],
133            line_len: 0,
134            iac_skip: 0,
135        }
136    }
137
138    /// Implements reset.
139    fn reset(&mut self) {
140        self.connected = false;
141        self.line_len = 0;
142        self.iac_skip = 0;
143    }
144}
145
146/// Implements send prompt.
147fn send_prompt(fd: usize) {
148    let _ = write_all(fd, b"\r\nstrat9> ");
149}
150
151/// Implements handle command.
152fn handle_command(fd: usize, line: &str) -> LineAction {
153    let cmd = line.trim();
154    if cmd.is_empty() {
155        send_prompt(fd);
156        return LineAction::Continue;
157    }
158
159    if cmd == "help" {
160        let _ = write_all(
161            fd,
162            b"\r\nCommands: help, ip, net, echo <text>, clear, quit\r\n",
163        );
164        send_prompt(fd);
165        return LineAction::Continue;
166    }
167
168    if cmd == "ip" {
169        let mut buf = [0u8; 128];
170        let n = read_text_file("/net/address", &mut buf);
171        let _ = write_all(fd, b"\r\nIP: ");
172        if n > 0 {
173            let _ = write_all(fd, &buf[..n]);
174        } else {
175            let _ = write_all(fd, b"n/a\r\n");
176        }
177        send_prompt(fd);
178        return LineAction::Continue;
179    }
180
181    if cmd == "net" {
182        let mut ip = [0u8; 128];
183        let mut gw = [0u8; 128];
184        let mut dns = [0u8; 128];
185        let mut route = [0u8; 128];
186        let nip = read_text_file("/net/ip", &mut ip);
187        let ngw = read_text_file("/net/gateway", &mut gw);
188        let ndns = read_text_file("/net/dns", &mut dns);
189        let nr = read_text_file("/net/route", &mut route);
190
191        let _ = write_all(fd, b"\r\nIP: ");
192        let _ = write_all(fd, if nip > 0 { &ip[..nip] } else { b"n/a\r\n" });
193        let _ = write_all(fd, b"GW: ");
194        let _ = write_all(fd, if ngw > 0 { &gw[..ngw] } else { b"n/a\r\n" });
195        let _ = write_all(fd, b"DNS: ");
196        let _ = write_all(fd, if ndns > 0 { &dns[..ndns] } else { b"n/a\r\n" });
197        let _ = write_all(fd, b"ROUTE: ");
198        let _ = write_all(fd, if nr > 0 { &route[..nr] } else { b"n/a\r\n" });
199        send_prompt(fd);
200        return LineAction::Continue;
201    }
202
203    if let Some(rest) = cmd.strip_prefix("echo ") {
204        let _ = write_all(fd, b"\r\n");
205        let _ = write_all(fd, rest.as_bytes());
206        let _ = write_all(fd, b"\r\n");
207        send_prompt(fd);
208        return LineAction::Continue;
209    }
210
211    if cmd == "clear" {
212        let _ = write_all(fd, b"\x1b[2J\x1b[H");
213        send_prompt(fd);
214        return LineAction::Continue;
215    }
216
217    if cmd == "quit" || cmd == "exit" {
218        let _ = write_all(fd, b"\r\nBye.\r\n");
219        return LineAction::Disconnect;
220    }
221
222    let _ = write_all(fd, b"\r\nUnknown command. Type 'help'.\r\n");
223    send_prompt(fd);
224    LineAction::Continue
225}
226
227/// Implements handle bytes.
228fn handle_bytes(fd: usize, session: &mut TelnetSession, bytes: &[u8]) -> LineAction {
229    for &b in bytes {
230        if session.iac_skip > 0 {
231            session.iac_skip -= 1;
232            continue;
233        }
234        if b == 255 {
235            session.iac_skip = 2;
236            continue;
237        }
238        if b == b'\r' {
239            continue;
240        }
241        if b == b'\n' {
242            let line = core::str::from_utf8(&session.line[..session.line_len]).unwrap_or("");
243            session.line_len = 0;
244            let action = handle_command(fd, line);
245            if matches!(action, LineAction::Disconnect) {
246                return action;
247            }
248            continue;
249        }
250        if session.line_len < session.line.len() {
251            session.line[session.line_len] = b;
252            session.line_len += 1;
253        }
254    }
255    LineAction::Continue
256}
257
258#[unsafe(no_mangle)]
259/// Implements start.
260pub extern "C" fn _start() -> ! {
261    log("[telnetd] Starting telnet server on /net/tcp/listen/23\n");
262    let mut fd = open_listener();
263    let mut session = TelnetSession::new();
264    let mut buf = [0u8; 128];
265
266    loop {
267        match call::read(fd, &mut buf) {
268            Ok(0) => {
269                if session.connected {
270                    let _ = call::close(fd);
271                    session.reset();
272                    fd = open_listener();
273                } else {
274                    sleep_ms(20);
275                }
276            }
277            Ok(n) => {
278                if !session.connected {
279                    session.connected = true;
280                    let _ = write_all(fd, b"\r\nStrat9 Telnet\r\nType 'help' for commands.\r\n");
281                    send_prompt(fd);
282                }
283                if matches!(
284                    handle_bytes(fd, &mut session, &buf[..n]),
285                    LineAction::Disconnect
286                ) {
287                    let _ = call::close(fd);
288                    session.reset();
289                    fd = open_listener();
290                }
291            }
292            Err(e) => {
293                if e.to_errno() == 11 {
294                    sleep_ms(10);
295                    continue;
296                }
297                let _ = call::close(fd);
298                session.reset();
299                sleep_ms(100);
300                fd = open_listener();
301            }
302        }
303    }
304}