Files
ESP32_Serial_Swiss_Army_Knife/src/admin_ssh_console.h
T
Commander1024 94433ef975 Add typed account and password settings
- Add admin account list, create, role, delete, and password workflows
- Execute identity-checked mutations through the existing dispatcher
- Bound queued credential lifetime and wipe transient secrets
- Add explicit password generation with saved-value acknowledgement
- Handle self-revocation and uncertain outcomes without automatic
  retries
- Register optional account routes without disrupting terminal
  transports
- Expand host regressions and document contracts and pending target
  checks

Validated host suites and pio run; hardware validation remains pending.
2026-09-08 09:27:02 +02:00

165 lines
7.9 KiB
C

/* SPDX-License-Identifier: GPL-3.0-only */
/* Bounded administrative dispatcher with a small remote-owner boundary. */
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#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);
/* 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