Replaced the SSH library + IO thread + intermediate PTY bridge with vte_terminal_spawn_sync(). This eliminates the broken terminal behavior (backspace, Ctrl+C, copy/paste, bash completion) caused by the intermediate PTY with no process on the slave side. - SshTerminal now spawns 'ssh' as a VTE subprocess - Removed libssh dependency from terminal (still used for key validation) - Removed IO thread, manual PTY creation, and thread synchronization - VTE now handles all terminal I/O, line discipline, resize, etc. - Password saved: uses sshpass -e with SSHPASS env var if available - Password not saved: ssh prompts interactively in the terminal - Removed show_password_dialog (no longer needed) - Simplified connect flow in ssh_client.cpp
161 lines
4.1 KiB
C++
161 lines
4.1 KiB
C++
#include "key_store.h"
|
|
#include <fstream>
|
|
#include <sstream>
|
|
#include <cstdlib>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
|
|
static std::string trim(const std::string& s) {
|
|
size_t a = s.find_first_not_of(" \t\r\n");
|
|
if (a == std::string::npos) return "";
|
|
size_t b = s.find_last_not_of(" \t\r\n");
|
|
return s.substr(a, b - a + 1);
|
|
}
|
|
|
|
static std::string get_fingerprint(const std::string& pubkey_path) {
|
|
std::string cmd = "ssh-keygen -lf \"" + pubkey_path + "\" 2>/dev/null";
|
|
FILE* pipe = popen(cmd.c_str(), "r");
|
|
if (!pipe) return "";
|
|
char buf[512];
|
|
std::string result;
|
|
if (fgets(buf, sizeof(buf), pipe)) {
|
|
result = trim(buf);
|
|
/* Remove the key type prefix like "(RSA)" */
|
|
auto paren = result.find('(');
|
|
if (paren != std::string::npos) {
|
|
result = trim(result.substr(0, paren));
|
|
}
|
|
}
|
|
pclose(pipe);
|
|
return result;
|
|
}
|
|
|
|
KeyStore::KeyStore() {
|
|
const char* home = getenv("HOME");
|
|
if (!home) home = ".";
|
|
config_path_ = std::string(home) + "/.config/sshclient/keys.conf";
|
|
keys_dir_ = std::string(home) + "/.config/sshclient/keys";
|
|
std::string cmd = "mkdir -p \"" + keys_dir_ + "\"";
|
|
system(cmd.c_str());
|
|
load();
|
|
}
|
|
|
|
void KeyStore::load() {
|
|
keys_.clear();
|
|
std::ifstream f(config_path_);
|
|
if (!f.is_open()) return;
|
|
|
|
SshKey current;
|
|
bool in_key = false;
|
|
std::string line;
|
|
|
|
while (std::getline(f, line)) {
|
|
line = trim(line);
|
|
if (line.empty() || line[0] == '#') {
|
|
if (in_key && !current.name.empty()) {
|
|
keys_.push_back(current);
|
|
current = SshKey();
|
|
in_key = false;
|
|
}
|
|
continue;
|
|
}
|
|
if (line[0] == '[' && line.back() == ']') {
|
|
if (in_key && !current.name.empty()) {
|
|
keys_.push_back(current);
|
|
current = SshKey();
|
|
}
|
|
current.name = trim(line.substr(1, line.size() - 2));
|
|
in_key = true;
|
|
continue;
|
|
}
|
|
if (!in_key) continue;
|
|
auto eq = line.find('=');
|
|
if (eq == std::string::npos) continue;
|
|
std::string key = trim(line.substr(0, eq));
|
|
std::string val = trim(line.substr(eq + 1));
|
|
if (key == "pubkey_path") current.pubkey_path = val;
|
|
else if (key == "privkey_path") current.privkey_path = val;
|
|
else if (key == "fingerprint") current.fingerprint = val;
|
|
}
|
|
if (in_key && !current.name.empty()) {
|
|
keys_.push_back(current);
|
|
}
|
|
}
|
|
|
|
void KeyStore::save() const {
|
|
std::ofstream f(config_path_);
|
|
if (!f.is_open()) return;
|
|
|
|
f << "# SSH Client - Stored Keys\n\n";
|
|
for (const auto& k : keys_) {
|
|
f << "[" << k.name << "]\n";
|
|
f << "pubkey_path=" << k.pubkey_path << "\n";
|
|
f << "privkey_path=" << k.privkey_path << "\n";
|
|
f << "fingerprint=" << k.fingerprint << "\n\n";
|
|
}
|
|
}
|
|
|
|
void KeyStore::add(const SshKey& k) {
|
|
keys_.push_back(k);
|
|
save();
|
|
}
|
|
|
|
void KeyStore::remove(int index) {
|
|
if (index >= 0 && index < (int)keys_.size()) {
|
|
keys_.erase(keys_.begin() + index);
|
|
save();
|
|
}
|
|
}
|
|
|
|
bool KeyStore::generate_keypair(const std::string& name, int bits) {
|
|
std::string priv_path = keys_dir_ + "/" + name;
|
|
std::string pub_path = priv_path + ".pub";
|
|
|
|
std::string cmd = "ssh-keygen -t rsa -b " + std::to_string(bits) +
|
|
" -f \"" + priv_path + "\" -N \"\" -C \"" + name + "\" 2>/dev/null";
|
|
int rc = system(cmd.c_str());
|
|
if (rc != 0) return false;
|
|
|
|
SshKey k;
|
|
k.name = name;
|
|
k.privkey_path = priv_path;
|
|
k.pubkey_path = pub_path;
|
|
k.fingerprint = get_fingerprint(pub_path);
|
|
|
|
add(k);
|
|
return true;
|
|
}
|
|
|
|
bool KeyStore::import_key(const std::string& name, const std::string& privkey_path) {
|
|
/* Validate key by running ssh-keygen -l */
|
|
std::string check_cmd = "ssh-keygen -l -f \"" + privkey_path + "\" >/dev/null 2>&1";
|
|
int rc = system(check_cmd.c_str());
|
|
if (rc != 0) return false;
|
|
|
|
std::string dest = keys_dir_ + "/" + name;
|
|
std::string cp_cmd = "cp \"" + privkey_path + "\" \"" + dest + "\" && chmod 600 \"" + dest + "\"";
|
|
rc = system(cp_cmd.c_str());
|
|
if (rc != 0) return false;
|
|
|
|
std::string pub_src = privkey_path + ".pub";
|
|
std::string pub_dest = dest + ".pub";
|
|
std::string cp_pub = "cp \"" + pub_src + "\" \"" + pub_dest + "\" 2>/dev/null";
|
|
system(cp_pub.c_str());
|
|
|
|
SshKey k;
|
|
k.name = name;
|
|
k.privkey_path = dest;
|
|
k.pubkey_path = pub_dest;
|
|
k.fingerprint = get_fingerprint(pub_dest);
|
|
|
|
add(k);
|
|
return true;
|
|
}
|
|
|
|
SshKey KeyStore::find_by_name(const std::string& name) const {
|
|
for (const auto& k : keys_) {
|
|
if (k.name == name) return k;
|
|
}
|
|
return {};
|
|
}
|