Public Access
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
80 lines
3.2 KiB
C++
80 lines
3.2 KiB
C++
#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
|