Expand admin SSH command capabilities
Add per-session history, tab completion, interactive prompts, and bounded input handling. Support deferred lifecycle and host-key actions after output drains, and document the expanded administration workflow.
This commit is contained in:
+434
-45
@@ -7,12 +7,16 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "console_completion.h"
|
||||
#include "esp_console.h"
|
||||
#include "esp_system.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/task.h"
|
||||
#include "linenoise/linenoise.h"
|
||||
#include "secure_random.h"
|
||||
#include "ssh_transport.h"
|
||||
#include "user_database.h"
|
||||
|
||||
#define ADMIN_SSH_CONSOLE_MAX_SESSIONS 2U
|
||||
@@ -24,15 +28,41 @@
|
||||
#define ADMIN_UART_CONSOLE_TASK_STACK_SIZE 6144U
|
||||
#define ADMIN_UART_CONSOLE_TASK_PRIORITY 3U
|
||||
#define ADMIN_SSH_CONSOLE_MAX_ARGUMENTS 10U
|
||||
#define ADMIN_SSH_CONSOLE_HISTORY_DEPTH 4U
|
||||
#define ADMIN_SSH_CONTROL_QUEUE_LENGTH 2U
|
||||
#define ADMIN_SSH_CONTROL_TASK_STACK_SIZE 4096U
|
||||
#define ADMIN_SSH_CONTROL_TASK_PRIORITY 3U
|
||||
|
||||
typedef enum {
|
||||
ADMIN_PROMPT_NONE = 0,
|
||||
ADMIN_PROMPT_WAITING,
|
||||
ADMIN_PROMPT_SUBMITTED,
|
||||
ADMIN_PROMPT_CANCELLED,
|
||||
ADMIN_PROMPT_DISCONNECTED,
|
||||
} admin_prompt_state_t;
|
||||
|
||||
typedef struct {
|
||||
bool active;
|
||||
bool command_pending;
|
||||
bool executing;
|
||||
bool deferred_action_pending;
|
||||
admin_ssh_console_token_t token;
|
||||
user_principal_t principal;
|
||||
size_t input_length;
|
||||
uint8_t input[ADMIN_SSH_CONSOLE_COMMAND_LINE_CAPACITY + 1U];
|
||||
uint8_t history[ADMIN_SSH_CONSOLE_HISTORY_DEPTH]
|
||||
[ADMIN_SSH_CONSOLE_COMMAND_LINE_CAPACITY + 1U];
|
||||
uint8_t history_count;
|
||||
int8_t history_position;
|
||||
uint8_t draft[ADMIN_SSH_CONSOLE_COMMAND_LINE_CAPACITY + 1U];
|
||||
size_t draft_length;
|
||||
uint8_t escape_state;
|
||||
bool discard_next_lf;
|
||||
admin_prompt_state_t prompt_state;
|
||||
bool prompt_hidden;
|
||||
size_t prompt_capacity;
|
||||
size_t prompt_length;
|
||||
uint8_t prompt_input[ADMIN_SSH_CONSOLE_COMMAND_LINE_CAPACITY + 1U];
|
||||
size_t output_start;
|
||||
size_t output_length;
|
||||
uint8_t output[ADMIN_SSH_CONSOLE_OUTPUT_CAPACITY];
|
||||
@@ -51,6 +81,12 @@ typedef struct {
|
||||
uint8_t line[ADMIN_SSH_CONSOLE_COMMAND_LINE_CAPACITY + 1U];
|
||||
} admin_request_t;
|
||||
|
||||
typedef struct {
|
||||
admin_ssh_deferred_action_type_t action;
|
||||
admin_ssh_console_token_t token;
|
||||
uint32_t argument;
|
||||
} admin_control_request_t;
|
||||
|
||||
static portMUX_TYPE s_lock = portMUX_INITIALIZER_UNLOCKED;
|
||||
static admin_session_t s_sessions[ADMIN_SSH_CONSOLE_MAX_SESSIONS];
|
||||
|
||||
@@ -58,14 +94,23 @@ static StaticQueue_t s_request_queue_storage;
|
||||
static uint8_t s_request_queue_bytes[ADMIN_SSH_CONSOLE_REQUEST_QUEUE_LENGTH *
|
||||
sizeof(admin_request_t)];
|
||||
static QueueHandle_t s_request_queue;
|
||||
static StaticQueue_t s_control_queue_storage;
|
||||
static uint8_t s_control_queue_bytes[ADMIN_SSH_CONTROL_QUEUE_LENGTH *
|
||||
sizeof(admin_control_request_t)];
|
||||
static QueueHandle_t s_control_queue;
|
||||
static StaticSemaphore_t s_prompt_done_storage;
|
||||
static SemaphoreHandle_t s_prompt_done;
|
||||
static TaskHandle_t s_task;
|
||||
static TaskHandle_t s_uart_task;
|
||||
static TaskHandle_t s_control_task;
|
||||
static bool s_initialized;
|
||||
static bool s_dispatch_ready;
|
||||
/* Accessed only by the single dispatcher task while a callback is running. */
|
||||
static bool s_dispatch_remote;
|
||||
static bool s_dispatch_output_previous_cr;
|
||||
static admin_ssh_console_token_t s_dispatch_token;
|
||||
static user_principal_t s_dispatch_principal;
|
||||
static ssh_transport_snapshot_t s_control_ssh_snapshot;
|
||||
|
||||
bool admin_ssh_console_dispatch_is_remote(void)
|
||||
{
|
||||
@@ -137,6 +182,158 @@ static void print_prompt(const admin_ssh_console_token_t *token)
|
||||
(void)worker_write(token, "admin@serial-tool> ");
|
||||
}
|
||||
|
||||
static bool redraw_line_locked(admin_session_t *session)
|
||||
{
|
||||
static const char prefix[] = "\r\x1b[2Kadmin@serial-tool> ";
|
||||
size_t required = sizeof(prefix) - 1U + session->input_length;
|
||||
if (required > ADMIN_SSH_CONSOLE_OUTPUT_CAPACITY - session->output_length) {
|
||||
return append_output_locked(session, (const uint8_t *)"\a", 1U);
|
||||
}
|
||||
(void)append_output_locked(session, (const uint8_t *)prefix, sizeof(prefix) - 1U);
|
||||
return append_output_locked(session, session->input, session->input_length);
|
||||
}
|
||||
|
||||
static void history_commit_locked(admin_session_t *session)
|
||||
{
|
||||
if (session->input_length == 0U ||
|
||||
(session->history_count > 0U &&
|
||||
strcmp((const char *)session->history[0], (const char *)session->input) == 0)) {
|
||||
return;
|
||||
}
|
||||
for (size_t index = ADMIN_SSH_CONSOLE_HISTORY_DEPTH - 1U; index > 0U; --index) {
|
||||
memcpy(session->history[index], session->history[index - 1U],
|
||||
sizeof(session->history[index]));
|
||||
}
|
||||
memcpy(session->history[0], session->input, sizeof(session->history[0]));
|
||||
if (session->history_count < ADMIN_SSH_CONSOLE_HISTORY_DEPTH) {
|
||||
++session->history_count;
|
||||
}
|
||||
}
|
||||
|
||||
static void history_move_locked(admin_session_t *session, bool older)
|
||||
{
|
||||
if (older) {
|
||||
if (session->history_count == 0U ||
|
||||
session->history_position + 1 >= (int8_t)session->history_count) {
|
||||
(void)append_output_locked(session, (const uint8_t *)"\a", 1U);
|
||||
return;
|
||||
}
|
||||
if (session->history_position < 0) {
|
||||
memcpy(session->draft, session->input, sizeof(session->draft));
|
||||
session->draft_length = session->input_length;
|
||||
}
|
||||
++session->history_position;
|
||||
memcpy(session->input, session->history[session->history_position],
|
||||
sizeof(session->input));
|
||||
session->input_length = strlen((const char *)session->input);
|
||||
} else {
|
||||
if (session->history_position < 0) {
|
||||
(void)append_output_locked(session, (const uint8_t *)"\a", 1U);
|
||||
return;
|
||||
}
|
||||
--session->history_position;
|
||||
if (session->history_position < 0) {
|
||||
memcpy(session->input, session->draft, sizeof(session->input));
|
||||
session->input_length = session->draft_length;
|
||||
} else {
|
||||
memcpy(session->input, session->history[session->history_position],
|
||||
sizeof(session->input));
|
||||
session->input_length = strlen((const char *)session->input);
|
||||
}
|
||||
}
|
||||
(void)redraw_line_locked(session);
|
||||
}
|
||||
|
||||
esp_err_t admin_ssh_console_dispatch_read_input(
|
||||
const char *prompt, uint8_t *output, size_t capacity,
|
||||
bool hidden, size_t *output_length)
|
||||
{
|
||||
if (!admin_ssh_console_dispatch_is_remote() || prompt == NULL || output == NULL ||
|
||||
output_length == NULL || capacity == 0U ||
|
||||
capacity > ADMIN_SSH_CONSOLE_COMMAND_LINE_CAPACITY + 1U) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
*output_length = 0U;
|
||||
memset(output, 0, capacity);
|
||||
(void)xSemaphoreTake(s_prompt_done, 0U);
|
||||
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
admin_session_t *session = &s_sessions[s_dispatch_token.slot_index];
|
||||
if (!token_matches(session, &s_dispatch_token) || !session->executing ||
|
||||
session->prompt_state != ADMIN_PROMPT_NONE) {
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
secure_wipe(session->prompt_input, sizeof(session->prompt_input));
|
||||
session->prompt_length = 0U;
|
||||
session->prompt_capacity = capacity;
|
||||
session->prompt_hidden = hidden;
|
||||
session->prompt_state = ADMIN_PROMPT_WAITING;
|
||||
bool published = append_output_locked(session, (const uint8_t *)prompt, strlen(prompt));
|
||||
if (!published) {
|
||||
session->prompt_state = ADMIN_PROMPT_NONE;
|
||||
session->prompt_capacity = 0U;
|
||||
}
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
if (!published) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
if (xSemaphoreTake(s_prompt_done, portMAX_DELAY) != pdTRUE) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
esp_err_t result = ESP_ERR_INVALID_STATE;
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
session = &s_sessions[s_dispatch_token.slot_index];
|
||||
if (session->prompt_state == ADMIN_PROMPT_SUBMITTED) {
|
||||
memcpy(output, session->prompt_input, session->prompt_length);
|
||||
*output_length = session->prompt_length;
|
||||
result = ESP_OK;
|
||||
} else if (session->prompt_state == ADMIN_PROMPT_DISCONNECTED) {
|
||||
result = ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
secure_wipe(session->prompt_input, sizeof(session->prompt_input));
|
||||
session->prompt_length = 0U;
|
||||
session->prompt_capacity = 0U;
|
||||
session->prompt_hidden = false;
|
||||
session->prompt_state = ADMIN_PROMPT_NONE;
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
return result;
|
||||
}
|
||||
|
||||
esp_err_t admin_ssh_console_dispatch_defer(
|
||||
admin_ssh_deferred_action_type_t action, uint32_t argument)
|
||||
{
|
||||
if (!admin_ssh_console_dispatch_is_remote() || action == ADMIN_SSH_DEFER_NONE) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
admin_session_t *session = &s_sessions[s_dispatch_token.slot_index];
|
||||
bool valid = token_matches(session, &s_dispatch_token) &&
|
||||
!session->deferred_action_pending;
|
||||
if (valid) {
|
||||
session->deferred_action_pending = true;
|
||||
}
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
if (!valid) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
admin_control_request_t request = {
|
||||
.action = action,
|
||||
.token = s_dispatch_token,
|
||||
.argument = argument,
|
||||
};
|
||||
if (xQueueSend(s_control_queue, &request, 0U) == pdTRUE) {
|
||||
return ESP_OK;
|
||||
}
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
session = &s_sessions[s_dispatch_token.slot_index];
|
||||
if (token_matches(session, &s_dispatch_token)) {
|
||||
session->deferred_action_pending = false;
|
||||
}
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
static int ssh_output_write(void *cookie, const char *buffer, int length)
|
||||
{
|
||||
@@ -193,42 +390,9 @@ static bool remote_command_allowed(const admin_request_t *request)
|
||||
/* Use exactly the same quote/escape parser as esp_console_run(). */
|
||||
size_t argc = esp_console_split_argv(copy, argv, ADMIN_SSH_CONSOLE_MAX_ARGUMENTS);
|
||||
bool allowed = argc > 0U;
|
||||
if (allowed && strcmp(argv[0], "reboot") == 0) {
|
||||
if (allowed && strcmp(argv[0], "user") == 0 && argc >= 2U &&
|
||||
(strcmp(argv[1], "bootstrap") == 0 || strcmp(argv[1], "recover") == 0)) {
|
||||
allowed = false;
|
||||
} else if (allowed && strcmp(argv[0], "ping") == 0) {
|
||||
allowed = false;
|
||||
} else if (allowed && strcmp(argv[0], "user") == 0 && argc >= 2U) {
|
||||
if (strcmp(argv[1], "bootstrap") == 0 || strcmp(argv[1], "recover") == 0) {
|
||||
allowed = false;
|
||||
} else if (strcmp(argv[1], "add") == 0) {
|
||||
allowed = argc == 5U && strcmp(argv[4], "--generate") == 0;
|
||||
} else if (strcmp(argv[1], "password") == 0) {
|
||||
allowed = argc == 4U && strcmp(argv[3], "--generate") == 0;
|
||||
} else if (strcmp(argv[1], "key") == 0 && argc >= 3U &&
|
||||
strcmp(argv[2], "add") == 0) {
|
||||
allowed = argc == 6U;
|
||||
}
|
||||
} else if (allowed && strcmp(argv[0], "wifi") == 0 && argc >= 2U) {
|
||||
if (strcmp(argv[1], "ping") == 0) {
|
||||
allowed = false;
|
||||
} else if (strcmp(argv[1], "profile") == 0 && argc >= 3U &&
|
||||
strcmp(argv[2], "secret") == 0) {
|
||||
allowed = false;
|
||||
} else if (strcmp(argv[1], "ap") == 0 && argc >= 3U &&
|
||||
(strcmp(argv[2], "secret") == 0 ||
|
||||
strcmp(argv[2], "show-secret") == 0)) {
|
||||
allowed = false;
|
||||
}
|
||||
} else if (allowed && strcmp(argv[0], "web") == 0 && argc >= 2U) {
|
||||
allowed = strcmp(argv[1], "credentials") != 0 &&
|
||||
strcmp(argv[1], "certificate") != 0 &&
|
||||
strcmp(argv[1], "reset") != 0;
|
||||
} else if (allowed && strcmp(argv[0], "ssh") == 0 && argc >= 2U) {
|
||||
allowed = strcmp(argv[1], "start") != 0 && strcmp(argv[1], "stop") != 0 &&
|
||||
strcmp(argv[1], "disconnect") != 0 && strcmp(argv[1], "reset") != 0;
|
||||
if (allowed && strcmp(argv[1], "host-key") == 0) {
|
||||
allowed = argc == 3U && strcmp(argv[2], "info") == 0;
|
||||
}
|
||||
}
|
||||
secure_wipe(copy, sizeof(copy));
|
||||
return allowed;
|
||||
@@ -262,6 +426,7 @@ static void dispatch_registered_command(admin_request_t *request)
|
||||
stderr = remote_stream;
|
||||
s_dispatch_output_previous_cr = false;
|
||||
s_dispatch_remote = true;
|
||||
s_dispatch_token = request->token;
|
||||
s_dispatch_principal = request->principal;
|
||||
} else {
|
||||
s_dispatch_remote = false;
|
||||
@@ -275,6 +440,7 @@ static void dispatch_registered_command(admin_request_t *request)
|
||||
|
||||
s_dispatch_remote = false;
|
||||
s_dispatch_output_previous_cr = false;
|
||||
secure_wipe(&s_dispatch_token, sizeof(s_dispatch_token));
|
||||
secure_wipe(&s_dispatch_principal, sizeof(s_dispatch_principal));
|
||||
if (remote_stream != NULL) {
|
||||
stdout = saved_stdout;
|
||||
@@ -329,7 +495,8 @@ static void worker_task(void *context)
|
||||
session->executing = false;
|
||||
session->command_pending = false;
|
||||
prompt = auth_error == ESP_OK && current &&
|
||||
request.principal.role == USER_ROLE_ADMIN;
|
||||
request.principal.role == USER_ROLE_ADMIN &&
|
||||
!session->deferred_action_pending;
|
||||
} else if (!session->active && session->executing &&
|
||||
token_identity_matches(session, &request.token)) {
|
||||
/* A disconnect invalidated this executing request; erase buffered secrets. */
|
||||
@@ -343,6 +510,100 @@ static void worker_task(void *context)
|
||||
}
|
||||
}
|
||||
|
||||
static void finish_deferred_request(const admin_control_request_t *request,
|
||||
esp_err_t result, bool cancelled)
|
||||
{
|
||||
char message[160];
|
||||
if (cancelled) {
|
||||
snprintf(message, sizeof(message),
|
||||
"Deferred action cancelled before SSH output drained.\r\nadmin@serial-tool> ");
|
||||
} else if (result == ESP_OK) {
|
||||
snprintf(message, sizeof(message),
|
||||
"Deferred SSH action completed.\r\nadmin@serial-tool> ");
|
||||
} else {
|
||||
snprintf(message, sizeof(message),
|
||||
"Deferred SSH action failed: %s\r\nadmin@serial-tool> ",
|
||||
esp_err_to_name(result));
|
||||
}
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
admin_session_t *session = &s_sessions[request->token.slot_index];
|
||||
if (token_matches(session, &request->token)) {
|
||||
(void)append_output_locked(session, (const uint8_t *)message, strlen(message));
|
||||
session->deferred_action_pending = false;
|
||||
}
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
}
|
||||
|
||||
static void control_task(void *context)
|
||||
{
|
||||
(void)context;
|
||||
for (;;) {
|
||||
admin_control_request_t request;
|
||||
if (xQueueReceive(s_control_queue, &request, portMAX_DELAY) != pdTRUE) {
|
||||
continue;
|
||||
}
|
||||
TickType_t deadline = xTaskGetTickCount() + pdMS_TO_TICKS(10000U);
|
||||
bool drained = false;
|
||||
while ((int32_t)(xTaskGetTickCount() - deadline) < 0) {
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
admin_session_t *session = &s_sessions[request.token.slot_index];
|
||||
bool current = token_matches(session, &request.token);
|
||||
bool console_drained = current && !session->command_pending &&
|
||||
session->output_length == 0U;
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
if (!current) {
|
||||
break;
|
||||
}
|
||||
bool transport_drained = false;
|
||||
if (console_drained &&
|
||||
ssh_transport_get_snapshot(&s_control_ssh_snapshot) == ESP_OK) {
|
||||
for (size_t index = 0U; index < SSH_TRANSPORT_MAX_SESSIONS; ++index) {
|
||||
const ssh_transport_session_snapshot_t *slot =
|
||||
&s_control_ssh_snapshot.sessions[index];
|
||||
if (slot->active && slot->session_id == request.token.session_id) {
|
||||
transport_drained = !slot->tx_pending;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (console_drained && transport_drained) {
|
||||
drained = true;
|
||||
break;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(10U));
|
||||
}
|
||||
if (!drained) {
|
||||
finish_deferred_request(&request, ESP_ERR_TIMEOUT, true);
|
||||
secure_wipe(&request, sizeof(request));
|
||||
continue;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(200U));
|
||||
esp_err_t result = ESP_OK;
|
||||
switch (request.action) {
|
||||
case ADMIN_SSH_DEFER_REBOOT:
|
||||
esp_restart();
|
||||
break;
|
||||
case ADMIN_SSH_DEFER_STOP:
|
||||
result = ssh_transport_stop();
|
||||
break;
|
||||
case ADMIN_SSH_DEFER_DISCONNECT:
|
||||
result = ssh_transport_disconnect(request.argument);
|
||||
break;
|
||||
case ADMIN_SSH_DEFER_HOST_KEY_ROTATE:
|
||||
result = ssh_transport_replace_host_key(false);
|
||||
break;
|
||||
case ADMIN_SSH_DEFER_HOST_KEY_RESET:
|
||||
result = ssh_transport_replace_host_key(true);
|
||||
break;
|
||||
default:
|
||||
result = ESP_ERR_NOT_SUPPORTED;
|
||||
break;
|
||||
}
|
||||
finish_deferred_request(&request, result, false);
|
||||
secure_wipe(&request, sizeof(request));
|
||||
}
|
||||
}
|
||||
|
||||
static void uart_frontend_task(void *context)
|
||||
{
|
||||
(void)context;
|
||||
@@ -388,7 +649,11 @@ esp_err_t admin_ssh_console_init(void)
|
||||
s_request_queue = xQueueCreateStatic(ADMIN_SSH_CONSOLE_REQUEST_QUEUE_LENGTH,
|
||||
sizeof(admin_request_t), s_request_queue_bytes,
|
||||
&s_request_queue_storage);
|
||||
if (s_request_queue == NULL) {
|
||||
s_control_queue = xQueueCreateStatic(ADMIN_SSH_CONTROL_QUEUE_LENGTH,
|
||||
sizeof(admin_control_request_t),
|
||||
s_control_queue_bytes, &s_control_queue_storage);
|
||||
s_prompt_done = xSemaphoreCreateBinaryStatic(&s_prompt_done_storage);
|
||||
if (s_request_queue == NULL || s_control_queue == NULL || s_prompt_done == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
if (xTaskCreate(worker_task, "admin_ssh_console", ADMIN_SSH_CONSOLE_TASK_STACK_SIZE,
|
||||
@@ -396,6 +661,13 @@ esp_err_t admin_ssh_console_init(void)
|
||||
s_task = NULL;
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
if (xTaskCreate(control_task, "admin_ssh_control", ADMIN_SSH_CONTROL_TASK_STACK_SIZE,
|
||||
NULL, ADMIN_SSH_CONTROL_TASK_PRIORITY, &s_control_task) != pdPASS) {
|
||||
s_control_task = NULL;
|
||||
vTaskDelete(s_task);
|
||||
s_task = NULL;
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
s_initialized = true;
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
@@ -450,6 +722,7 @@ esp_err_t admin_ssh_console_open(const admin_ssh_console_token_t *token,
|
||||
}
|
||||
secure_wipe(session, sizeof(*session));
|
||||
session->active = true;
|
||||
session->history_position = -1;
|
||||
session->token = *token;
|
||||
session->principal = *principal;
|
||||
static const char banner[] =
|
||||
@@ -470,13 +743,21 @@ void admin_ssh_console_close(const admin_ssh_console_token_t *token)
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
admin_session_t *session = &s_sessions[token->slot_index];
|
||||
bool matched = token_matches(session, token);
|
||||
bool wake_prompt = false;
|
||||
if (matched) {
|
||||
if (session->prompt_state == ADMIN_PROMPT_WAITING) {
|
||||
session->prompt_state = ADMIN_PROMPT_DISCONNECTED;
|
||||
wake_prompt = true;
|
||||
}
|
||||
session->active = false;
|
||||
if (!session->executing) {
|
||||
secure_wipe(session, sizeof(*session));
|
||||
}
|
||||
}
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
if (wake_prompt) {
|
||||
(void)xSemaphoreGive(s_prompt_done);
|
||||
}
|
||||
}
|
||||
|
||||
bool admin_ssh_console_accepts_input(const admin_ssh_console_token_t *token)
|
||||
@@ -486,7 +767,10 @@ bool admin_ssh_console_accepts_input(const admin_ssh_console_token_t *token)
|
||||
}
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
admin_session_t *session = &s_sessions[token->slot_index];
|
||||
bool accepts = token_matches(session, token) && !session->command_pending &&
|
||||
bool shell_input = !session->command_pending && !session->deferred_action_pending;
|
||||
bool prompt_input = session->command_pending && session->executing &&
|
||||
session->prompt_state == ADMIN_PROMPT_WAITING;
|
||||
bool accepts = token_matches(session, token) && (shell_input || prompt_input) &&
|
||||
session->output_length <= ADMIN_SSH_CONSOLE_OUTPUT_CAPACITY -
|
||||
ADMIN_SSH_CONSOLE_RESPONSE_RESERVE;
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
@@ -506,40 +790,143 @@ bool admin_ssh_console_feed_input(const admin_ssh_console_token_t *token,
|
||||
bool submit = false;
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
admin_session_t *session = &s_sessions[token->slot_index];
|
||||
if (!token_matches(session, token) || session->command_pending) {
|
||||
if (!token_matches(session, token)) {
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
return *consumed != 0U;
|
||||
}
|
||||
uint8_t value = data[index];
|
||||
if (session->command_pending) {
|
||||
if (!session->executing || session->prompt_state != ADMIN_PROMPT_WAITING) {
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
return *consumed != 0U;
|
||||
}
|
||||
bool wake_prompt = false;
|
||||
if (session->discard_next_lf && value == '\n') {
|
||||
session->discard_next_lf = false;
|
||||
} else {
|
||||
session->discard_next_lf = false;
|
||||
if (value == '\r' || value == '\n') {
|
||||
session->discard_next_lf = value == '\r';
|
||||
session->prompt_state = ADMIN_PROMPT_SUBMITTED;
|
||||
(void)append_output_locked(session, (const uint8_t *)"\r\n", 2U);
|
||||
wake_prompt = true;
|
||||
} else if (value == 0x03U) {
|
||||
secure_wipe(session->prompt_input, sizeof(session->prompt_input));
|
||||
session->prompt_length = 0U;
|
||||
session->prompt_state = ADMIN_PROMPT_CANCELLED;
|
||||
(void)append_output_locked(session, (const uint8_t *)"^C\r\n", 4U);
|
||||
wake_prompt = true;
|
||||
} else if (value == 0x08U || value == 0x7fU) {
|
||||
if (session->prompt_length > 0U) {
|
||||
session->prompt_input[--session->prompt_length] = 0U;
|
||||
if (!session->prompt_hidden) {
|
||||
(void)append_output_locked(session,
|
||||
(const uint8_t *)"\b \b", 3U);
|
||||
}
|
||||
}
|
||||
} else if (value >= 0x20U && value <= 0x7eU) {
|
||||
if (session->prompt_length + 1U < session->prompt_capacity) {
|
||||
session->prompt_input[session->prompt_length++] = value;
|
||||
if (!session->prompt_hidden) {
|
||||
(void)append_output_locked(session, &value, 1U);
|
||||
}
|
||||
} else {
|
||||
(void)append_output_locked(session, (const uint8_t *)"\a", 1U);
|
||||
}
|
||||
}
|
||||
}
|
||||
++*consumed;
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
if (wake_prompt) {
|
||||
(void)xSemaphoreGive(s_prompt_done);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (session->output_length >= ADMIN_SSH_CONSOLE_OUTPUT_CAPACITY -
|
||||
ADMIN_SSH_CONSOLE_RESPONSE_RESERVE) {
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
return *consumed != 0U;
|
||||
}
|
||||
if (value == '\r' || value == '\n') {
|
||||
if (value == '\n' && session->input_length == 0U) {
|
||||
++*consumed;
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
continue;
|
||||
if (session->discard_next_lf && value == '\n') {
|
||||
session->discard_next_lf = false;
|
||||
++*consumed;
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
continue;
|
||||
}
|
||||
session->discard_next_lf = false;
|
||||
if (session->escape_state != 0U) {
|
||||
if (session->escape_state == 1U && (value == '[' || value == 'O')) {
|
||||
session->escape_state = 2U;
|
||||
} else if (session->escape_state == 2U) {
|
||||
if (value == 'A' || value == 'B') {
|
||||
history_move_locked(session, value == 'A');
|
||||
}
|
||||
session->escape_state = 0U;
|
||||
} else {
|
||||
session->escape_state = 0U;
|
||||
}
|
||||
++*consumed;
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
continue;
|
||||
}
|
||||
if (value == 0x1bU) {
|
||||
session->escape_state = 1U;
|
||||
++*consumed;
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
continue;
|
||||
}
|
||||
if (value == '\t') {
|
||||
char current[ADMIN_SSH_CONSOLE_COMMAND_LINE_CAPACITY + 1U];
|
||||
memcpy(current, session->input, sizeof(current));
|
||||
++*consumed;
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
char completed[ADMIN_SSH_CONSOLE_COMMAND_LINE_CAPACITY + 1U] = {0};
|
||||
bool expanded = console_completion_expand(current, completed,
|
||||
sizeof(completed));
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
session = &s_sessions[token->slot_index];
|
||||
if (token_matches(session, token) && !session->command_pending &&
|
||||
memcmp(current, session->input, sizeof(current)) == 0) {
|
||||
if (expanded) {
|
||||
strlcpy((char *)session->input, completed, sizeof(session->input));
|
||||
session->input_length = strlen((const char *)session->input);
|
||||
session->history_position = -1;
|
||||
(void)redraw_line_locked(session);
|
||||
} else {
|
||||
(void)append_output_locked(session, (const uint8_t *)"\a", 1U);
|
||||
}
|
||||
}
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
secure_wipe(current, sizeof(current));
|
||||
secure_wipe(completed, sizeof(completed));
|
||||
continue;
|
||||
}
|
||||
if (value == '\r' || value == '\n') {
|
||||
session->discard_next_lf = value == '\r';
|
||||
history_commit_locked(session);
|
||||
memcpy(request.line, session->input, session->input_length);
|
||||
request.origin = ADMIN_REQUEST_SSH;
|
||||
request.token = *token;
|
||||
request.principal = session->principal;
|
||||
secure_wipe(session->input, sizeof(session->input));
|
||||
session->input_length = 0U;
|
||||
session->history_position = -1;
|
||||
session->command_pending = true;
|
||||
(void)append_output_locked(session, (const uint8_t *)"\r\n",
|
||||
sizeof("\r\n") - 1U);
|
||||
submit = true;
|
||||
} else if (value == 0x03U) {
|
||||
secure_wipe(session->input, sizeof(session->input));
|
||||
session->input_length = 0U;
|
||||
session->history_position = -1;
|
||||
(void)append_output_locked(session, (const uint8_t *)"^C\r\n",
|
||||
sizeof("^C\r\n") - 1U);
|
||||
(void)append_output_locked(session, (const uint8_t *)"admin@serial-tool> ",
|
||||
sizeof("admin@serial-tool> ") - 1U);
|
||||
} else if (value == 0x08U || value == 0x7fU) {
|
||||
if (session->input_length > 0U) {
|
||||
--session->input_length;
|
||||
session->input[--session->input_length] = 0U;
|
||||
session->history_position = -1;
|
||||
(void)append_output_locked(session, (const uint8_t *)"\b \b",
|
||||
sizeof("\b \b") - 1U);
|
||||
}
|
||||
@@ -550,7 +937,9 @@ bool admin_ssh_console_feed_input(const admin_ssh_console_token_t *token,
|
||||
"\r\nCommand too long; discarded.\r\nadmin@serial-tool> ",
|
||||
sizeof("\r\nCommand too long; discarded.\r\nadmin@serial-tool> ") - 1U);
|
||||
} else {
|
||||
session->history_position = -1;
|
||||
session->input[session->input_length++] = value;
|
||||
session->input[session->input_length] = 0U;
|
||||
(void)append_output_locked(session, &value, 1U);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user