/* SPDX-License-Identifier: GPL-3.0-only */ /* Bounded administrative dispatcher with a small remote-owner boundary. */ #pragma once #include #include #include #include "esp_err.h" #include "user_database.h" #ifdef __cplusplus extern "C" { #endif /* Nonblocking typed settings admission to the canonical dispatcher. */ esp_err_t admin_ssh_console_submit_serial_settings(uint32_t id); esp_err_t admin_ssh_console_submit_account_settings(uint32_t id); esp_err_t admin_ssh_console_submit_network_settings(uint32_t id); esp_err_t admin_ssh_console_submit_display_settings(uint32_t id); esp_err_t admin_ssh_console_submit_broker_settings(uint32_t id); esp_err_t admin_ssh_console_submit_ssh_settings(uint32_t id); /* Fits the longest supported ECDSA P-256 OpenSSH key import command. */ #define ADMIN_SSH_CONSOLE_COMMAND_LINE_CAPACITY 256U #define ADMIN_CONSOLE_TRANSPORT_SSH 0U #define ADMIN_CONSOLE_TRANSPORT_WEB 1U typedef struct { uint8_t slot_index; uint32_t session_id; uint32_t slot_generation; uint8_t transport; /* Zero is SSH, including legacy designated initializers. */ } admin_ssh_console_token_t; typedef enum { ADMIN_SSH_DEFER_NONE = 0, ADMIN_SSH_DEFER_REBOOT, ADMIN_SSH_DEFER_STOP, ADMIN_SSH_DEFER_DISCONNECT, ADMIN_SSH_DEFER_HOST_KEY_ROTATE, ADMIN_SSH_DEFER_HOST_KEY_RESET, ADMIN_CONSOLE_DEFER_SELF_CLOSE, ADMIN_CONSOLE_DEFER_WEB_STOP, ADMIN_CONSOLE_DEFER_WEB_CERTIFICATE_ROTATE, } admin_ssh_deferred_action_type_t; /* Small owner boundary; module/API names are retained for existing SSH callers. * Exactly two shared console slots, not two per transport. slot_index addresses * this pool; open_available atomically selects a free slot. Owners must not reuse an identity while * old work can exist. transport is a firmware-assigned namespace (0 = SSH). * An occupied or still-executing slot cannot be replaced by open_owned(). * * The immutable adapter lives for firmware lifetime. Callbacks run on the * control task OUTSIDE console locks for drained/perform, except perform actions * selected by dispatcher_actions run serialized on the existing 12KiB dispatcher * after drain/delay and queued identity/principal revalidation (no command replay). * Zero dispatcher_actions preserves legacy control-task execution. Required is_current * runs on the dispatcher outside console locks; it must be bounded and validate * full transport identity, originating-session liveness and principal binding, * without calling socket libraries or handlers. Core separately checks accounts. * drained must be nonblocking, validate the full identity and include pending * owner output. perform must revalidate identity and marshal lifecycle work to * its owner, never call socket libraries here. Neither callback may call console * handlers. supported_actions is a bitmask (1U << action); reject unsupported * actions before side effects. Legacy STOP/DISCONNECT/key actions mean SSH; * SELF_CLOSE means this frontend; WEB_STOP means HTTPS, not SSH. * WEB_CERTIFICATE_ROTATE replaces the HTTPS identity and restarts HTTPS. * These WEB actions and SELF_CLOSE ignore argument. * * One owner serializes feed calls per session; different owners may feed in * parallel. Shared completion scratch is nonblocking/serialized by the core. * The owner alone consumes output, maintains authentication/session liveness, * and calls close on disconnect/revocation. Core copies/rechecks principals at * admission and dispatch. Dispatch and prompts also check owner currentness; * blocked prompts recheck every 250ms (plus check/scheduling latency). This does * not cancel or roll back arbitrary executing handlers. Admission remains the * owner's responsibility; is_current need not accept unpublished admission. * Close wakes prompts; executing state is retained until the handler returns. * Output remains bounded (5s write backpressure); deferred work waits at most * 10s for application drain plus 200ms, NOT peer-delivery confirmation. * Dispatcher actions then wait behind queued commands/prompts, with input gated * until completion or cancellation; the drain bound is not an execution deadline. * No new tasks, queues, slots, or browser endpoint are provided by this API. */ typedef struct { uint32_t supported_actions; uint32_t dispatcher_actions; /* Subset of supported_actions; immutable. */ bool (*is_current)(const admin_ssh_console_token_t *token, const user_principal_t *principal); bool (*drained)(const admin_ssh_console_token_t *token); esp_err_t (*perform)(const admin_ssh_console_token_t *token, admin_ssh_deferred_action_type_t action, uint32_t argument); } admin_console_owner_t; /* Selects any inactive, nonexecuting slot from the shared two-slot pool. * Input slot_index is ignored; only slot_index changes, and only on success. * Caller supplies transport/session_id/slot_generation and must retain the * returned token. Full pool returns ESP_ERR_INVALID_STATE, like open_owned. */ esp_err_t admin_ssh_console_open_available(admin_ssh_console_token_t *token, const user_principal_t *principal, const admin_console_owner_t *owner); esp_err_t admin_ssh_console_open_owned(const admin_ssh_console_token_t *token, const user_principal_t *principal, const admin_console_owner_t *owner); typedef struct { bool active; bool command_pending; bool input_pending; bool output_pending; bool deferred_action_pending; size_t input_length; size_t output_length; } admin_ssh_console_session_snapshot_t; /* Starts the single command worker. It is the sole esp_console_run() caller. */ esp_err_t admin_ssh_console_init(void); /* Register administration-shell-only root commands after esp_console initialization. */ esp_err_t admin_ssh_console_register_commands(void); /* Called after all ESP-IDF commands are registered; starts the UART0 frontend. */ esp_err_t admin_ssh_console_start_uart_frontend(void); /* Valid only while a registered command callback runs on the dispatcher task. */ bool admin_ssh_console_dispatch_is_remote(void); bool admin_ssh_console_dispatch_is_web(void); const user_principal_t *admin_ssh_console_dispatch_principal(void); /* Revalidate account, originating owner/session and token before side effects. * False outside the dispatcher; UART0 dispatch remains physically trusted. */ bool admin_ssh_console_dispatch_is_current(void); /* Shared parsed browser account policy: dispatcher admission + handler defense. */ bool admin_ssh_console_web_user_command_allowed( size_t argc, char **argv, const user_principal_t *principal); esp_err_t admin_ssh_console_dispatch_read_input( const char *prompt, uint8_t *output, size_t capacity, bool hidden, size_t *output_length); esp_err_t admin_ssh_console_dispatch_defer( admin_ssh_deferred_action_type_t action, uint32_t argument); /* SSH compatibility entry point, implemented by the owner in ssh_transport.c. * Token/principal are copied; no SSH or socket objects cross this boundary. * Existing feed/close/read/snapshot APIs below also accept open_owned tokens. */ esp_err_t admin_ssh_console_open(const admin_ssh_console_token_t *token, const user_principal_t *principal); void admin_ssh_console_close(const admin_ssh_console_token_t *token); /* Called by the session owner. Returns false when input must be backpressured. */ bool admin_ssh_console_accepts_input(const admin_ssh_console_token_t *token); bool admin_ssh_console_feed_input(const admin_ssh_console_token_t *token, const uint8_t *data, size_t length, size_t *consumed); /* Called by the session owner; copies already-produced output without blocking. */ esp_err_t admin_ssh_console_read_output(const admin_ssh_console_token_t *token, uint8_t *data, size_t capacity, size_t *received); esp_err_t admin_ssh_console_get_session_snapshot( const admin_ssh_console_token_t *token, admin_ssh_console_session_snapshot_t *snapshot); #ifdef __cplusplus } #endif