Public Access
SSH client: a terminal on another machine (#2)
The SSH App opens one session to a shell, over libssh (LibSSH-ESP32 5.10.0) on mbedTLS. A server is trusted the first time on its fingerprint, and a changed key is a warning with Cancel selected. The password is typed each time and kept nowhere; or the device makes itself an Ed25519 key, whose public half is shown, written to /ssh/id_ed25519.pub and printed by `ssh status`. lib/term is the terminal: what a shell, less, top, nano and vim send, with sixteen colours, scroll regions, the alternate screen and 100 lines of scrollback. Five text sizes with Ctrl and + or -, from 60x20 to 26x8, told to the far end. The session goes on when the App is left; SSH shows in the Status Bar. `ssh user@host` in the Shell opens the App. Also: - Keys that aren't characters carry Shift, Ctrl and Alt. The terminal needs it, and it makes Ctrl+Fn+up/down in a note and Shift+Tab in Gemini work from the real keyboard. - IRC doesn't try to connect under 60 KB free: started with a session open, its TLS handshake took the heap down to 236 bytes. - libssh's own curve25519 is left out of the build (scripts/libssh_filter.py): libsodium's has the same names. Costs 292 KB of flash and about 50 KB of heap while a session is open; not started under 75 KB free. Docs: guide page, how-to, FAQ, home page, Status Bar, SD card folders, the memory how-to, README, glossary, N1 notes with Q254 to Q266 and the checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "event_bus.h"
|
||||
#include "settings.h"
|
||||
#include "ssh_hosts.h"
|
||||
#include "terminal.h"
|
||||
|
||||
namespace roro {
|
||||
|
||||
// One SSH session to a shell (issue #2, docs/milestones/N1.md): the protocol is libssh's, on a
|
||||
// task of its own; what it prints goes into a term::Terminal, which the SSH App draws. The
|
||||
// session lasts until the far end closes it or disconnect() is called, whether the App is in
|
||||
// front or not.
|
||||
//
|
||||
// The task and the main loop share the terminal, the bytes waiting to be sent and the state,
|
||||
// under one lock. Questions for the user (is this the right server? what is the password?) are
|
||||
// states the task waits in until the App answers.
|
||||
class SshService {
|
||||
public:
|
||||
enum class State { Idle, Connecting, AskTrust, AskPassword, Open, Ended };
|
||||
static constexpr size_t kNeedFree = 75 * 1024; // a session peaks at about 63 KB (measured)
|
||||
static constexpr int kHistory = 100;
|
||||
|
||||
explicit SshService(Settings& settings);
|
||||
|
||||
// "" when the connection is on its way, or why not.
|
||||
std::string connect(const term::SshTarget& target, int cols, int rows);
|
||||
void disconnect(); // from any state; the terminal stays to be read until the next connect
|
||||
void clear(); // Ended, and read: the terminal and its history are given back
|
||||
State state() const { return state_; }
|
||||
bool active() const { return state_ == State::Open; } // for the Status Bar
|
||||
bool opened() const { return opened_; } // this session got as far as a shell
|
||||
std::string status() const; // what it is doing, or why it ended
|
||||
const term::SshTarget& target() const { return target_; }
|
||||
|
||||
// AskTrust: the server's fingerprint, and the one remembered if this is a different one.
|
||||
std::string fingerprint() const;
|
||||
std::string remembered() const;
|
||||
void answerTrust(bool yes);
|
||||
// AskPassword.
|
||||
void answerPassword(const std::string& password);
|
||||
|
||||
// Open (and Ended, to read what is left).
|
||||
bool hasTerminal() const { return static_cast<bool>(term_); }
|
||||
void withTerminal(const std::function<void(term::Terminal&)>& use);
|
||||
uint32_t revision() const { return revision_; }
|
||||
void send(const std::string& bytes);
|
||||
void resize(int cols, int rows);
|
||||
|
||||
// This device's own key: made once, its public half is what goes on servers.
|
||||
bool hasKey() const { return !settings_.getString(Setting::SshKey).empty(); }
|
||||
std::string publicKey() const { return settings_.getString(Setting::SshPublic); }
|
||||
std::string makeKey(); // "" or why not
|
||||
|
||||
private:
|
||||
struct Run; // what one connection's task works with
|
||||
static void task(void* arg);
|
||||
void session(Run& run);
|
||||
void end(const std::string& why);
|
||||
void say(const std::string& what);
|
||||
|
||||
Settings& settings_;
|
||||
void* lock_; // SemaphoreHandle_t
|
||||
std::atomic<State> state_{State::Idle};
|
||||
std::atomic<bool> stop_{false}, answered_{false}, trusted_{false}, running_{false}, opened_{false};
|
||||
std::atomic<uint32_t> revision_{0};
|
||||
term::SshTarget target_;
|
||||
std::unique_ptr<term::Terminal> term_;
|
||||
std::string status_, fingerprint_, remembered_, password_, outgoing_;
|
||||
int wantCols_ = 0, wantRows_ = 0;
|
||||
bool resized_ = false;
|
||||
};
|
||||
|
||||
} // namespace roro
|
||||
Reference in New Issue
Block a user