mirror of
https://github.com/BoredDevNL/BoredOS.git
synced 2026-05-15 10:48:38 +00:00
basic telnet
This commit is contained in:
parent
cc752052dc
commit
903d4e0510
8 changed files with 563 additions and 13 deletions
BIN
boredos.iso
BIN
boredos.iso
Binary file not shown.
BIN
build/cmd.o
BIN
build/cmd.o
Binary file not shown.
144
src/kernel/cmd.c
144
src/kernel/cmd.c
|
|
@ -37,6 +37,7 @@
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char c;
|
char c;
|
||||||
uint32_t color;
|
uint32_t color;
|
||||||
|
uint8_t attrs; // bit 0: bold, bit 1: reverse, bit 2: blink
|
||||||
} CharCell;
|
} CharCell;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
|
|
@ -113,6 +114,11 @@ static int ansi_state = 0;
|
||||||
static int ansi_params[ANSI_MAX_PARAMS];
|
static int ansi_params[ANSI_MAX_PARAMS];
|
||||||
static int ansi_param_count = 0;
|
static int ansi_param_count = 0;
|
||||||
static bool ansi_private_mode = false;
|
static bool ansi_private_mode = false;
|
||||||
|
static int saved_cursor_row = 0;
|
||||||
|
static int saved_cursor_col = 0;
|
||||||
|
static uint8_t current_attrs = 0;
|
||||||
|
static bool cursor_visible = true;
|
||||||
|
static bool terminal_raw_mode = false;
|
||||||
|
|
||||||
// Pager State
|
// Pager State
|
||||||
static CmdMode current_mode = MODE_SHELL;
|
static CmdMode current_mode = MODE_SHELL;
|
||||||
|
|
@ -172,11 +178,22 @@ static void ansi_handle_sgr() {
|
||||||
if (ansi_param_count == 0) {
|
if (ansi_param_count == 0) {
|
||||||
current_color = 0xFFFFFFFF;
|
current_color = 0xFFFFFFFF;
|
||||||
current_bg_color = 0;
|
current_bg_color = 0;
|
||||||
|
current_attrs = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (int i = 0; i < ansi_param_count; i++) {
|
for (int i = 0; i < ansi_param_count; i++) {
|
||||||
int p = ansi_params[i];
|
int p = ansi_params[i];
|
||||||
if (p == 0) { current_color = 0xFFFFFFFF; current_bg_color = 0; }
|
if (p == 0) {
|
||||||
|
current_color = 0xFFFFFFFF;
|
||||||
|
current_bg_color = 0;
|
||||||
|
current_attrs = 0;
|
||||||
|
}
|
||||||
|
else if (p == 1) current_attrs |= 1; // Bold
|
||||||
|
else if (p == 5) current_attrs |= 4; // Blink
|
||||||
|
else if (p == 7) current_attrs |= 2; // Reverse
|
||||||
|
else if (p == 22) current_attrs &= ~1; // Normal intensity
|
||||||
|
else if (p == 25) current_attrs &= ~4; // Stop blink
|
||||||
|
else if (p == 27) current_attrs &= ~2; // Normal (not reverse)
|
||||||
else if (p >= 30 && p <= 37) current_color = ansi_get_256_color(p - 30);
|
else if (p >= 30 && p <= 37) current_color = ansi_get_256_color(p - 30);
|
||||||
else if (p >= 40 && p <= 47) current_bg_color = ansi_get_256_color(p - 40);
|
else if (p >= 40 && p <= 47) current_bg_color = ansi_get_256_color(p - 40);
|
||||||
else if (p >= 90 && p <= 97) current_color = ansi_get_256_color(p - 90 + 8);
|
else if (p >= 90 && p <= 97) current_color = ansi_get_256_color(p - 90 + 8);
|
||||||
|
|
@ -615,6 +632,10 @@ void cmd_putchar(char c) {
|
||||||
if (c == '\x1b') {
|
if (c == '\x1b') {
|
||||||
ansi_state = 1;
|
ansi_state = 1;
|
||||||
return;
|
return;
|
||||||
|
} else if (c == '\x07') {
|
||||||
|
// BEL - Beep
|
||||||
|
k_beep(750, 100);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} else if (ansi_state == 1) {
|
} else if (ansi_state == 1) {
|
||||||
if (c == '[') {
|
if (c == '[') {
|
||||||
|
|
@ -623,6 +644,32 @@ void cmd_putchar(char c) {
|
||||||
for(int i=0; i<ANSI_MAX_PARAMS; i++) ansi_params[i] = 0;
|
for(int i=0; i<ANSI_MAX_PARAMS; i++) ansi_params[i] = 0;
|
||||||
ansi_private_mode = false;
|
ansi_private_mode = false;
|
||||||
return;
|
return;
|
||||||
|
} else if (c == 'O') {
|
||||||
|
ansi_state = 3; // SS3
|
||||||
|
return;
|
||||||
|
} else if (c == 'M') {
|
||||||
|
// Reverse Index - scroll up if at top
|
||||||
|
if (cursor_row > 0) {
|
||||||
|
cursor_row--;
|
||||||
|
} else {
|
||||||
|
// Scroll screen down
|
||||||
|
for (int r = terminal_rows - 1; r > 0; r--) {
|
||||||
|
for (int col = 0; col < terminal_cols; col++) {
|
||||||
|
screen_buffer[r * terminal_cols + col] = screen_buffer[(r - 1) * terminal_cols + col];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int col = 0; col < terminal_cols; col++) {
|
||||||
|
screen_buffer[0 * terminal_cols + col].c = ' ';
|
||||||
|
screen_buffer[0 * terminal_cols + col].color = shell_config.default_text_color;
|
||||||
|
screen_buffer[0 * terminal_cols + col].attrs = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ansi_state = 0;
|
||||||
|
return;
|
||||||
|
} else if (c == '=' || c == '>') {
|
||||||
|
// ALT/NORM Keypad mode - absorb
|
||||||
|
ansi_state = 0;
|
||||||
|
return;
|
||||||
} else {
|
} else {
|
||||||
ansi_state = 0; // Unsupported ESC sequence
|
ansi_state = 0; // Unsupported ESC sequence
|
||||||
}
|
}
|
||||||
|
|
@ -663,20 +710,67 @@ void cmd_putchar(char c) {
|
||||||
} else if (c == 'D') {
|
} else if (c == 'D') {
|
||||||
int n = ansi_params[0] > 0 ? ansi_params[0] : 1;
|
int n = ansi_params[0] > 0 ? ansi_params[0] : 1;
|
||||||
cursor_col -= n; if (cursor_col < 0) cursor_col = 0;
|
cursor_col -= n; if (cursor_col < 0) cursor_col = 0;
|
||||||
|
} else if (c == 's') {
|
||||||
|
saved_cursor_row = cursor_row;
|
||||||
|
saved_cursor_col = cursor_col;
|
||||||
|
} else if (c == 'u') {
|
||||||
|
cursor_row = saved_cursor_row;
|
||||||
|
cursor_col = saved_cursor_col;
|
||||||
} else if (c == 'J') {
|
} else if (c == 'J') {
|
||||||
if (ansi_params[0] == 2) {
|
if (ansi_params[0] == 2) {
|
||||||
cmd_screen_clear();
|
cmd_screen_clear();
|
||||||
|
} else if (ansi_params[0] == 0) {
|
||||||
|
// Erase from cursor to end of screen
|
||||||
|
for (int i = cursor_row * terminal_cols + cursor_col; i < terminal_rows * terminal_cols; i++) {
|
||||||
|
screen_buffer[i].c = ' ';
|
||||||
|
screen_buffer[i].color = current_color;
|
||||||
|
screen_buffer[i].attrs = 0;
|
||||||
|
}
|
||||||
|
} else if (ansi_params[0] == 1) {
|
||||||
|
// Erase from beginning of screen to cursor
|
||||||
|
for (int i = 0; i <= cursor_row * terminal_cols + cursor_col; i++) {
|
||||||
|
screen_buffer[i].c = ' ';
|
||||||
|
screen_buffer[i].color = current_color;
|
||||||
|
screen_buffer[i].attrs = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (c == 'K') {
|
} else if (c == 'K') {
|
||||||
|
if (ansi_params[0] == 0) {
|
||||||
for (int i = cursor_col; i < terminal_cols; i++) {
|
for (int i = cursor_col; i < terminal_cols; i++) {
|
||||||
screen_buffer[cursor_row * terminal_cols + i].c = ' ';
|
screen_buffer[cursor_row * terminal_cols + i].c = ' ';
|
||||||
screen_buffer[cursor_row * terminal_cols + i].color = current_color;
|
screen_buffer[cursor_row * terminal_cols + i].color = current_color;
|
||||||
|
screen_buffer[cursor_row * terminal_cols + i].attrs = current_attrs;
|
||||||
}
|
}
|
||||||
|
} else if (ansi_params[0] == 1) {
|
||||||
|
for (int i = 0; i <= cursor_col; i++) {
|
||||||
|
screen_buffer[cursor_row * terminal_cols + i].c = ' ';
|
||||||
|
screen_buffer[cursor_row * terminal_cols + i].color = current_color;
|
||||||
|
screen_buffer[cursor_row * terminal_cols + i].attrs = current_attrs;
|
||||||
|
}
|
||||||
|
} else if (ansi_params[0] == 2) {
|
||||||
|
for (int i = 0; i < terminal_cols; i++) {
|
||||||
|
screen_buffer[cursor_row * terminal_cols + i].c = ' ';
|
||||||
|
screen_buffer[cursor_row * terminal_cols + i].color = current_color;
|
||||||
|
screen_buffer[cursor_row * terminal_cols + i].attrs = current_attrs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (c == 'h' && ansi_private_mode) {
|
||||||
|
if (ansi_params[0] == 25) cursor_visible = true;
|
||||||
|
} else if (c == 'l' && ansi_private_mode) {
|
||||||
|
if (ansi_params[0] == 25) cursor_visible = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ansi_state = 0;
|
ansi_state = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
} else if (ansi_state == 3) {
|
||||||
|
// SS3 sequences: ESC O <char>
|
||||||
|
if (c == 'A') { cursor_row--; if (cursor_row < 0) cursor_row = 0; }
|
||||||
|
else if (c == 'B') { cursor_row++; if (cursor_row >= terminal_rows) cursor_row = terminal_rows - 1; }
|
||||||
|
else if (c == 'C') { cursor_col++; if (cursor_col >= terminal_cols) cursor_col = terminal_cols - 1; }
|
||||||
|
else if (c == 'D') { cursor_col--; if (cursor_col < 0) cursor_col = 0; }
|
||||||
|
ansi_state = 0;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (c == '\n') {
|
if (c == '\n') {
|
||||||
|
|
@ -707,7 +801,7 @@ void cmd_putchar(char c) {
|
||||||
|
|
||||||
screen_buffer[cursor_row * terminal_cols + cursor_col].c = c;
|
screen_buffer[cursor_row * terminal_cols + cursor_col].c = c;
|
||||||
screen_buffer[cursor_row * terminal_cols + cursor_col].color = current_color;
|
screen_buffer[cursor_row * terminal_cols + cursor_col].color = current_color;
|
||||||
// Optionally set background color if we support it in CharCell
|
screen_buffer[cursor_row * terminal_cols + cursor_col].attrs = current_attrs;
|
||||||
cursor_col++;
|
cursor_col++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1787,7 +1881,7 @@ static void cmd_paint(Window *win) {
|
||||||
draw_string(start_x, start_y + (terminal_rows * LINE_HEIGHT), "-- Press Q to quit --", shell_config.default_text_color);
|
draw_string(start_x, start_y + (terminal_rows * LINE_HEIGHT), "-- Press Q to quit --", shell_config.default_text_color);
|
||||||
} else {
|
} else {
|
||||||
// Draw Cursor
|
// Draw Cursor
|
||||||
if (win->focused) {
|
if (win->focused && cursor_visible) {
|
||||||
draw_rect(start_x + (cursor_col * CHAR_WIDTH), start_y + (cursor_row * LINE_HEIGHT),
|
draw_rect(start_x + (cursor_col * CHAR_WIDTH), start_y + (cursor_row * LINE_HEIGHT),
|
||||||
CHAR_WIDTH, LINE_HEIGHT, shell_config.cursor_color);
|
CHAR_WIDTH, LINE_HEIGHT, shell_config.cursor_color);
|
||||||
}
|
}
|
||||||
|
|
@ -1795,14 +1889,27 @@ static void cmd_paint(Window *win) {
|
||||||
// Draw Shell Buffer
|
// Draw Shell Buffer
|
||||||
for (int r = 0; r < terminal_rows; r++) {
|
for (int r = 0; r < terminal_rows; r++) {
|
||||||
for (int c = 0; c < terminal_cols; c++) {
|
for (int c = 0; c < terminal_cols; c++) {
|
||||||
char ch = screen_buffer[r * terminal_cols + c].c;
|
CharCell cell = screen_buffer[r * terminal_cols + c];
|
||||||
|
char ch = cell.c;
|
||||||
if (ch != 0 && ch != ' ') {
|
if (ch != 0 && ch != ' ') {
|
||||||
uint32_t color = screen_buffer[r * terminal_cols + c].color;
|
uint32_t fg = cell.color;
|
||||||
// If cursor is on this character, and cursor color is bright, use background color for char
|
uint32_t bg = shell_config.bg_color;
|
||||||
if (r == cursor_row && c == cursor_col && win->focused) {
|
|
||||||
color = shell_config.bg_color;
|
if (cell.attrs & 2) { // Reverse
|
||||||
|
uint32_t tmp = fg;
|
||||||
|
fg = bg;
|
||||||
|
bg = tmp;
|
||||||
|
// Draw background for reversed text
|
||||||
|
draw_rect(start_x + (c * CHAR_WIDTH), start_y + (r * LINE_HEIGHT),
|
||||||
|
CHAR_WIDTH, LINE_HEIGHT, bg);
|
||||||
}
|
}
|
||||||
draw_char_bitmap(start_x + (c * CHAR_WIDTH), start_y + (r * LINE_HEIGHT), ch, color);
|
|
||||||
|
// If cursor is on this character, and cursor color is bright, use background color for char
|
||||||
|
if (r == cursor_row && c == cursor_col && win->focused && cursor_visible) {
|
||||||
|
fg = bg; // Character takes background color when cursor is over it
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_char_bitmap(start_x + (c * CHAR_WIDTH), start_y + (r * LINE_HEIGHT), ch, fg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1893,8 +2000,27 @@ void cmd_handle_click(Window *win, int x, int y) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void cmd_set_raw_mode(bool enabled) {
|
||||||
|
terminal_raw_mode = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
static void cmd_key(Window *target, char c) {
|
static void cmd_key(Window *target, char c) {
|
||||||
(void)target;
|
(void)target;
|
||||||
|
|
||||||
|
if (terminal_raw_mode) {
|
||||||
|
// Find the process associated with this terminal and push a key event
|
||||||
|
extern process_t* process_get_by_ui_window(void* win);
|
||||||
|
extern void process_push_gui_event(process_t *proc, gui_event_t *ev);
|
||||||
|
process_t *proc = process_get_by_ui_window(&win_cmd);
|
||||||
|
if (proc && proc->is_terminal_proc) {
|
||||||
|
gui_event_t ev;
|
||||||
|
ev.type = GUI_EVENT_KEY;
|
||||||
|
ev.arg1 = c;
|
||||||
|
process_push_gui_event(proc, &ev);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (current_mode == MODE_PAGER) {
|
if (current_mode == MODE_PAGER) {
|
||||||
if (c == 'q' || c == 'Q') {
|
if (c == 'q' || c == 'Q') {
|
||||||
current_mode = MODE_SHELL;
|
current_mode = MODE_SHELL;
|
||||||
|
|
|
||||||
|
|
@ -110,3 +110,15 @@ void k_reboot(void) {
|
||||||
void k_shutdown(void) {
|
void k_shutdown(void) {
|
||||||
outw(0x604, 0x2000);
|
outw(0x604, 0x2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void k_beep(int freq, int ms) {
|
||||||
|
if (freq <= 0) return;
|
||||||
|
int div = 1193180 / freq;
|
||||||
|
outb(0x43, 0xB6);
|
||||||
|
outb(0x42, div & 0xFF);
|
||||||
|
outb(0x42, (div >> 8) & 0xFF);
|
||||||
|
outb(0x61, inb(0x61) | 0x03);
|
||||||
|
k_sleep(ms);
|
||||||
|
outb(0x61, inb(0x61) & 0xFC);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,5 +22,6 @@ void k_delay(int iterations);
|
||||||
void k_sleep(int ms);
|
void k_sleep(int ms);
|
||||||
void k_reboot(void);
|
void k_reboot(void);
|
||||||
void k_shutdown(void);
|
void k_shutdown(void);
|
||||||
|
void k_beep(int freq, int ms);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -907,6 +907,10 @@ static uint64_t syscall_handler_inner(uint64_t syscall_num, uint64_t arg1, uint6
|
||||||
path[i] = 0;
|
path[i] = 0;
|
||||||
graphics_set_font(path);
|
graphics_set_font(path);
|
||||||
return 0;
|
return 0;
|
||||||
|
} else if (cmd == 41) { // SYSTEM_CMD_SET_RAW_MODE
|
||||||
|
extern void cmd_set_raw_mode(bool enabled);
|
||||||
|
cmd_set_raw_mode((bool)arg2);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@
|
||||||
#define SYSTEM_CMD_DNS_LOOKUP 37
|
#define SYSTEM_CMD_DNS_LOOKUP 37
|
||||||
#define SYSTEM_CMD_SET_DNS 38
|
#define SYSTEM_CMD_SET_DNS 38
|
||||||
#define SYSTEM_CMD_NET_UNLOCK 39
|
#define SYSTEM_CMD_NET_UNLOCK 39
|
||||||
|
#define SYSTEM_CMD_SET_RAW_MODE 41
|
||||||
|
|
||||||
// Internal assembly entry into Ring 0
|
// Internal assembly entry into Ring 0
|
||||||
extern uint64_t syscall0(uint64_t sys_num);
|
extern uint64_t syscall0(uint64_t sys_num);
|
||||||
|
|
|
||||||
406
src/kernel/userland/telnet.c
Normal file
406
src/kernel/userland/telnet.c
Normal file
|
|
@ -0,0 +1,406 @@
|
||||||
|
// Copyright (c) 2023-2026 Chris (boreddevnl)
|
||||||
|
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
|
||||||
|
// This header needs to maintain in any file it is present in, as per the GPL license terms.
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <syscall.h>
|
||||||
|
#include "libc/libui.h"
|
||||||
|
|
||||||
|
|
||||||
|
static int term_cols = 116;
|
||||||
|
static int term_rows = 41;
|
||||||
|
|
||||||
|
#define IAC 255
|
||||||
|
#define DONT 254
|
||||||
|
#define DO 253
|
||||||
|
#define WONT 252
|
||||||
|
#define WILL 251
|
||||||
|
#define SB 250 // sub-negotiation begin
|
||||||
|
#define SE 240 // sub-negotiation end
|
||||||
|
#define GA 249
|
||||||
|
#define EL 248
|
||||||
|
#define EC 247
|
||||||
|
#define AYT 246
|
||||||
|
#define AO 245
|
||||||
|
#define IP 244
|
||||||
|
#define BRK 243
|
||||||
|
#define DM 242
|
||||||
|
#define NOP 241
|
||||||
|
|
||||||
|
// Telnet options
|
||||||
|
#define OPT_ECHO 1
|
||||||
|
#define OPT_SUPPRESS_GA 3
|
||||||
|
#define OPT_STATUS 5
|
||||||
|
#define OPT_TIMING_MARK 6
|
||||||
|
#define OPT_NAWS 31 // Negotiate About Window Size
|
||||||
|
#define OPT_NEW_ENVIRON 39
|
||||||
|
#define OPT_TERMINAL_TYPE 24
|
||||||
|
|
||||||
|
// ─── IAC send helpers ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
static void telnet_send(const uint8_t *data, int len) {
|
||||||
|
sys_tcp_send(data, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void telnet_send_3(uint8_t a, uint8_t b, uint8_t c) {
|
||||||
|
uint8_t buf[3] = { a, b, c };
|
||||||
|
telnet_send(buf, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send NAWS subnegotiation with current terminal dimensions
|
||||||
|
static void telnet_send_naws(void) {
|
||||||
|
uint8_t buf[9];
|
||||||
|
buf[0] = IAC;
|
||||||
|
buf[1] = SB;
|
||||||
|
buf[2] = OPT_NAWS;
|
||||||
|
buf[3] = (uint8_t)((term_cols >> 8) & 0xFF);
|
||||||
|
buf[4] = (uint8_t)(term_cols & 0xFF);
|
||||||
|
buf[5] = (uint8_t)((term_rows >> 8) & 0xFF);
|
||||||
|
buf[6] = (uint8_t)(term_rows & 0xFF);
|
||||||
|
buf[7] = IAC;
|
||||||
|
buf[8] = SE;
|
||||||
|
telnet_send(buf, 9);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void telnet_handle_option(uint8_t cmd, uint8_t opt) {
|
||||||
|
switch (cmd) {
|
||||||
|
case DO:
|
||||||
|
if (opt == OPT_NAWS) {
|
||||||
|
telnet_send_3(IAC, WILL, OPT_NAWS);
|
||||||
|
telnet_send_naws();
|
||||||
|
} else if (opt == OPT_TERMINAL_TYPE) {
|
||||||
|
telnet_send_3(IAC, WILL, OPT_TERMINAL_TYPE);
|
||||||
|
} else {
|
||||||
|
telnet_send_3(IAC, WONT, opt);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DONT:
|
||||||
|
telnet_send_3(IAC, WONT, opt);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WILL:
|
||||||
|
if (opt == OPT_SUPPRESS_GA) {
|
||||||
|
// Good — accept suppressed GA (most BBS systems do this)
|
||||||
|
telnet_send_3(IAC, DO, OPT_SUPPRESS_GA);
|
||||||
|
} else if (opt == OPT_ECHO) {
|
||||||
|
// Server will echo chars (remote echo) — accept it
|
||||||
|
telnet_send_3(IAC, DO, OPT_ECHO);
|
||||||
|
} else {
|
||||||
|
// Refuse other server offers
|
||||||
|
telnet_send_3(IAC, DONT, opt);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WONT:
|
||||||
|
telnet_send_3(IAC, DONT, opt);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void telnet_handle_sb_terminal_type(const uint8_t *sb_data, int sb_len) {
|
||||||
|
if (sb_len < 1 || sb_data[0] != 1) return; // 1 = SEND
|
||||||
|
uint8_t reply[12];
|
||||||
|
int i = 0;
|
||||||
|
reply[i++] = IAC;
|
||||||
|
reply[i++] = SB;
|
||||||
|
reply[i++] = OPT_TERMINAL_TYPE;
|
||||||
|
reply[i++] = 0; // IS
|
||||||
|
reply[i++] = 'A'; reply[i++] = 'N'; reply[i++] = 'S'; reply[i++] = 'I';
|
||||||
|
reply[i++] = IAC;
|
||||||
|
reply[i++] = SE;
|
||||||
|
telnet_send(reply, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
TS_DATA = 0,
|
||||||
|
TS_IAC,
|
||||||
|
TS_CMD,
|
||||||
|
TS_OPT,
|
||||||
|
TS_SB,
|
||||||
|
TS_SB_IAC
|
||||||
|
} TelnetParseState;
|
||||||
|
|
||||||
|
static TelnetParseState ts_state = TS_DATA;
|
||||||
|
static uint8_t ts_cmd = 0;
|
||||||
|
static uint8_t ts_sb_opt = 0;
|
||||||
|
static uint8_t ts_sb_buf[256];
|
||||||
|
static int ts_sb_pos = 0;
|
||||||
|
|
||||||
|
// Output buffer — accumulate non-IAC bytes to write in bulk
|
||||||
|
static char out_buf[4096];
|
||||||
|
static int out_pos = 0;
|
||||||
|
|
||||||
|
static void flush_out(void) {
|
||||||
|
if (out_pos > 0) {
|
||||||
|
sys_write(1, out_buf, out_pos);
|
||||||
|
out_pos = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void out_char(char c) {
|
||||||
|
if (out_pos >= (int)(sizeof(out_buf) - 1)) {
|
||||||
|
flush_out();
|
||||||
|
}
|
||||||
|
out_buf[out_pos++] = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process a chunk of raw TCP data from server. Returns false if connection lost.
|
||||||
|
static int telnet_process(const uint8_t *data, int len) {
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
uint8_t b = data[i];
|
||||||
|
|
||||||
|
switch (ts_state) {
|
||||||
|
case TS_DATA:
|
||||||
|
if (b == IAC) {
|
||||||
|
ts_state = TS_IAC;
|
||||||
|
} else {
|
||||||
|
// Pass directly to display
|
||||||
|
out_char((char)b);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TS_IAC:
|
||||||
|
switch (b) {
|
||||||
|
case IAC:
|
||||||
|
// Escaped IAC — literal 0xFF
|
||||||
|
out_char((char)0xFF);
|
||||||
|
ts_state = TS_DATA;
|
||||||
|
break;
|
||||||
|
case DO: case DONT: case WILL: case WONT:
|
||||||
|
ts_cmd = b;
|
||||||
|
ts_state = TS_OPT;
|
||||||
|
break;
|
||||||
|
case SB:
|
||||||
|
ts_sb_pos = 0;
|
||||||
|
ts_state = TS_CMD;
|
||||||
|
break;
|
||||||
|
case GA: case NOP: case DM: case BRK: case IP:
|
||||||
|
case AO: case AYT: case EC: case EL:
|
||||||
|
ts_state = TS_DATA;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ts_state = TS_DATA;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TS_CMD:
|
||||||
|
ts_sb_opt = b;
|
||||||
|
ts_sb_pos = 0;
|
||||||
|
ts_state = TS_SB;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TS_OPT:
|
||||||
|
flush_out();
|
||||||
|
telnet_handle_option(ts_cmd, b);
|
||||||
|
ts_state = TS_DATA;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TS_SB:
|
||||||
|
if (b == IAC) {
|
||||||
|
ts_state = TS_SB_IAC;
|
||||||
|
} else {
|
||||||
|
if (ts_sb_pos < (int)sizeof(ts_sb_buf) - 1) {
|
||||||
|
ts_sb_buf[ts_sb_pos++] = b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TS_SB_IAC:
|
||||||
|
if (b == SE) {
|
||||||
|
flush_out();
|
||||||
|
if (ts_sb_opt == OPT_TERMINAL_TYPE) {
|
||||||
|
telnet_handle_sb_terminal_type(ts_sb_buf, ts_sb_pos);
|
||||||
|
}
|
||||||
|
ts_state = TS_DATA;
|
||||||
|
} else if (b == IAC) {
|
||||||
|
if (ts_sb_pos < (int)sizeof(ts_sb_buf) - 1) {
|
||||||
|
ts_sb_buf[ts_sb_pos++] = IAC;
|
||||||
|
}
|
||||||
|
ts_state = TS_SB;
|
||||||
|
} else {
|
||||||
|
ts_state = TS_DATA;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flush_out();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int map_key(char c, uint8_t *key_out) {
|
||||||
|
if (c == 29) {
|
||||||
|
// Ctrl+]
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (c == 17) {
|
||||||
|
// UP arrow
|
||||||
|
key_out[0] = 0x1b; key_out[1] = '['; key_out[2] = 'A';
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
if (c == 18) {
|
||||||
|
// DOWN arrow
|
||||||
|
key_out[0] = 0x1b; key_out[1] = '['; key_out[2] = 'B';
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
if (c == 20) {
|
||||||
|
// RIGHT arrow
|
||||||
|
key_out[0] = 0x1b; key_out[1] = '['; key_out[2] = 'C';
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
if (c == 19) {
|
||||||
|
// LEFT arrow
|
||||||
|
key_out[0] = 0x1b; key_out[1] = '['; key_out[2] = 'D';
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
if (c == '\n') {
|
||||||
|
// Enter
|
||||||
|
key_out[0] = '\r'; key_out[1] = '\n';
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
if (c == '\b') {
|
||||||
|
// Backspace
|
||||||
|
key_out[0] = '\x7f';
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
// Normal printable character
|
||||||
|
key_out[0] = (uint8_t)c;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int my_atoi(const char *s) {
|
||||||
|
int v = 0;
|
||||||
|
while (*s >= '0' && *s <= '9') { v = v*10 + (*s - '0'); s++; }
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parse_ip(const char *s, net_ipv4_address_t *ip) {
|
||||||
|
int part = 0, val = 0;
|
||||||
|
while (*s) {
|
||||||
|
if (*s >= '0' && *s <= '9') {
|
||||||
|
val = val*10 + (*s - '0');
|
||||||
|
if (val > 255) return -1;
|
||||||
|
} else if (*s == '.') {
|
||||||
|
if (part > 3) return -1;
|
||||||
|
ip->bytes[part++] = (uint8_t)val;
|
||||||
|
val = 0;
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
if (part != 3) return -1;
|
||||||
|
ip->bytes[3] = (uint8_t)val;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
if (argc < 2) {
|
||||||
|
printf("Usage: telnet <host> [port]\n");
|
||||||
|
printf(" Connect to a Telnet BBS or server.\n");
|
||||||
|
printf(" Default port: 23\n");
|
||||||
|
printf(" Press Ctrl+] to disconnect.\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *host = argv[1];
|
||||||
|
int port = (argc >= 3) ? my_atoi(argv[2]) : 23;
|
||||||
|
if (port <= 0 || port > 65535) port = 23;
|
||||||
|
|
||||||
|
if (!sys_network_is_initialized()) {
|
||||||
|
printf("Initializing network...\n");
|
||||||
|
sys_network_init();
|
||||||
|
}
|
||||||
|
if (!sys_network_has_ip()) {
|
||||||
|
printf("Acquiring DHCP...\n");
|
||||||
|
if (sys_network_dhcp_acquire() != 0) {
|
||||||
|
printf("DHCP failed.\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
net_ipv4_address_t ip;
|
||||||
|
if (parse_ip(host, &ip) != 0) {
|
||||||
|
printf("Resolving %s...\n", host);
|
||||||
|
if (sys_dns_lookup(host, &ip) != 0) {
|
||||||
|
printf("Failed to resolve: %s\n", host);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Connecting to %s:%d...\n", host, port);
|
||||||
|
if (sys_tcp_connect(&ip, (uint16_t)port) != 0) {
|
||||||
|
printf("Connection failed.\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
printf("Connected. Press Ctrl+] to disconnect.\n\n");
|
||||||
|
|
||||||
|
sys_system(41, 1, 0, 0, 0); // SYSTEM_CMD_SET_RAW_MODE = 1
|
||||||
|
|
||||||
|
uint8_t recv_buf[4096];
|
||||||
|
int total = 0;
|
||||||
|
int idle_count = 0;
|
||||||
|
int connected = 1;
|
||||||
|
|
||||||
|
while (connected) {
|
||||||
|
gui_event_t ev;
|
||||||
|
while (ui_get_event(0, &ev)) { // win=0 for console proc
|
||||||
|
if (ev.type == GUI_EVENT_KEY) {
|
||||||
|
uint8_t key_data[16];
|
||||||
|
int key_len = map_key((char)ev.arg1, key_data);
|
||||||
|
if (key_len < 0) {
|
||||||
|
connected = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
telnet_send(key_data, key_len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!connected) break;
|
||||||
|
|
||||||
|
int len = sys_tcp_recv(recv_buf, sizeof(recv_buf) - 1);
|
||||||
|
if (len < 0) {
|
||||||
|
printf("\r\n[Connection error]\r\n");
|
||||||
|
connected = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (len == 0) {
|
||||||
|
idle_count++;
|
||||||
|
// Don't timeout too fast if we are just waiting for user input
|
||||||
|
if (idle_count > 10000000) {
|
||||||
|
printf("\r\n[Connection timed out]\r\n");
|
||||||
|
connected = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Brief spin
|
||||||
|
for (volatile int d = 0; d < 100; d++);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
idle_count = 0;
|
||||||
|
total += len;
|
||||||
|
|
||||||
|
if (total > 10000000) {
|
||||||
|
printf("\r\n[Data limit reached]\r\n");
|
||||||
|
connected = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!telnet_process(recv_buf, len)) {
|
||||||
|
connected = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable raw mode before exiting
|
||||||
|
sys_system(41, 0, 0, 0, 0);
|
||||||
|
|
||||||
|
sys_tcp_close();
|
||||||
|
printf("\r\n[Telnet session ended]\r\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Reference in a new issue