Remote: native SSH/SFTP client, all uploads use it
This commit is contained in:
+1
-1
@@ -4,7 +4,7 @@ set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
find_package(SQLite3 REQUIRED)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
add_library(versiond STATIC src/config.cpp src/db.cpp src/store.cpp src/util.cpp src/ingest.cpp src/monitor.cpp src/maintenance.cpp src/recovery.cpp src/restore.cpp src/stats.cpp src/remote.cpp src/uploader.cpp src/api.cpp src/cli.cpp src/dbsafe.cpp src/filters.cpp src/diff.cpp src/impl.cpp)
|
||||
add_library(versiond STATIC src/config.cpp src/db.cpp src/store.cpp src/util.cpp src/ingest.cpp src/monitor.cpp src/maintenance.cpp src/recovery.cpp src/restore.cpp src/stats.cpp src/remote.cpp src/sftp_native.cpp src/uploader.cpp src/api.cpp src/cli.cpp src/dbsafe.cpp src/filters.cpp src/diff.cpp src/impl.cpp)
|
||||
target_include_directories(versiond PUBLIC include ${SQLite3_INCLUDE_DIRS})
|
||||
target_link_libraries(versiond PUBLIC SQLite::SQLite3 OpenSSL::Crypto)
|
||||
add_executable(versiond-cli src/main.cpp)
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
namespace versiond {
|
||||
class NativeSftp {
|
||||
public:
|
||||
static bool put(const std::string& host, const std::string& port, const std::string& user,
|
||||
const std::string& password, const std::string& local, const std::string& remote,
|
||||
std::string& err);
|
||||
static bool put(const std::string& host, const std::string& user, const std::string& local,
|
||||
const std::string& remote, std::string& err);
|
||||
};
|
||||
} // namespace versiond
|
||||
+26
-63
@@ -1,78 +1,41 @@
|
||||
#include "versiond/versiond.hpp"
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include "versiond/sftp_native.hpp"
|
||||
#include <filesystem>
|
||||
#include <sstream>
|
||||
namespace fs = std::filesystem;
|
||||
namespace versiond {
|
||||
namespace {
|
||||
struct Target {
|
||||
std::string user, host, port = "22", path;
|
||||
};
|
||||
Target parse_target(const std::string& url, const std::string& user, const std::string& fallback_remote) {
|
||||
Target t;
|
||||
std::string s = url.empty() ? fallback_remote : url;
|
||||
const std::string pre = "sftp://";
|
||||
if (s.rfind(pre, 0) == 0) s = s.substr(pre.size());
|
||||
auto at = s.find('@');
|
||||
std::string hostpart = s;
|
||||
if (at != std::string::npos) {
|
||||
t.user = s.substr(0, at);
|
||||
hostpart = s.substr(at + 1);
|
||||
} else {
|
||||
t.user = user;
|
||||
}
|
||||
auto slash = hostpart.find('/');
|
||||
std::string hp = slash == std::string::npos ? hostpart : hostpart.substr(0, slash);
|
||||
std::string p = slash == std::string::npos ? "" : hostpart.substr(slash);
|
||||
auto colon = hp.rfind(':');
|
||||
if (colon != std::string::npos && hp.find(']') == std::string::npos) {
|
||||
t.host = hp.substr(0, colon);
|
||||
t.port = hp.substr(colon + 1);
|
||||
} else {
|
||||
t.host = hp;
|
||||
}
|
||||
t.path = p.empty() ? fallback_remote : p;
|
||||
return t;
|
||||
}
|
||||
std::string shellq(const std::string& s) {
|
||||
std::string o = "'";
|
||||
for (char c : s) {
|
||||
if (c == '\'') o += "'\\''";
|
||||
else o += c;
|
||||
}
|
||||
return o + "'";
|
||||
}
|
||||
} // namespace
|
||||
bool remote_upload_wolfssh(const std::string& host, const std::string& user, const std::string& local,
|
||||
const std::string& remote, std::string& err) {
|
||||
if (!fs::exists(local)) {
|
||||
err = "local file missing: " + local;
|
||||
return false;
|
||||
}
|
||||
Target t = parse_target(host, user, remote);
|
||||
if (t.host.empty()) {
|
||||
std::string h = host, port = "22", u = user, path = remote;
|
||||
const std::string pre = "sftp://";
|
||||
if (h.rfind(pre, 0) == 0) h = h.substr(pre.size());
|
||||
auto at = h.find('@');
|
||||
if (at != std::string::npos) {
|
||||
if (u.empty()) u = h.substr(0, at);
|
||||
h = h.substr(at + 1);
|
||||
}
|
||||
auto slash = h.find('/');
|
||||
if (slash != std::string::npos) {
|
||||
if (path.empty() || path == remote) {
|
||||
}
|
||||
std::string p = h.substr(slash);
|
||||
h = h.substr(0, slash);
|
||||
if (!p.empty()) path = p;
|
||||
}
|
||||
auto colon = h.rfind(':');
|
||||
if (colon != std::string::npos) {
|
||||
port = h.substr(colon + 1);
|
||||
h = h.substr(0, colon);
|
||||
}
|
||||
if (h.empty()) {
|
||||
err = "no remote host";
|
||||
return false;
|
||||
}
|
||||
std::string dest = t.user.empty() ? t.host : t.user + "@" + t.host;
|
||||
std::string cmd = "sftp -P " + shellq(t.port) + " -o BatchMode=yes -o StrictHostKeyChecking=accept-new " +
|
||||
shellq(dest) + " <<'VERSIOND_SFTP_EOF'\nput " + shellq(local) + " " + shellq(t.path) +
|
||||
"\nVERSIOND_SFTP_EOF 2>&1";
|
||||
std::array<char, 512> buf{};
|
||||
std::string out;
|
||||
FILE* p = popen(cmd.c_str(), "r");
|
||||
if (!p) {
|
||||
err = "popen(sftp) failed";
|
||||
return false;
|
||||
}
|
||||
while (fgets(buf.data(), (int)buf.size(), p)) out += buf.data();
|
||||
int rc = pclose(p);
|
||||
if (rc != 0) {
|
||||
err = "sftp exit " + std::to_string(rc) + ": " + out;
|
||||
return false;
|
||||
}
|
||||
err = "";
|
||||
return true;
|
||||
const char* pw = std::getenv("VERSIOND_SFTP_PASSWORD");
|
||||
std::string pass = pw ? pw : "";
|
||||
return NativeSftp::put(h, port, u, pass, local, path, err);
|
||||
}
|
||||
} // namespace versiond
|
||||
|
||||
@@ -0,0 +1,554 @@
|
||||
#include "versiond/sftp_native.hpp"
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/hmac.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
namespace versiond {
|
||||
namespace {
|
||||
struct Sock {
|
||||
int fd = -1;
|
||||
~Sock() { if (fd >= 0) ::close(fd); }
|
||||
};
|
||||
bool wfull(int fd, const uint8_t* p, size_t n) {
|
||||
while (n) {
|
||||
ssize_t w = send(fd, p, n, MSG_NOSIGNAL);
|
||||
if (w <= 0) return false;
|
||||
p += w;
|
||||
n -= (size_t)w;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool rfull(int fd, uint8_t* p, size_t n) {
|
||||
while (n) {
|
||||
ssize_t r = recv(fd, p, n, 0);
|
||||
if (r <= 0) return false;
|
||||
p += r;
|
||||
n -= (size_t)r;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void u32(std::vector<uint8_t>& v, uint32_t x) {
|
||||
v.push_back(x >> 24);
|
||||
v.push_back(x >> 16);
|
||||
v.push_back(x >> 8);
|
||||
v.push_back((uint8_t)x);
|
||||
}
|
||||
void str(std::vector<uint8_t>& v, const std::string& s) {
|
||||
u32(v, (uint32_t)s.size());
|
||||
v.insert(v.end(), s.begin(), s.end());
|
||||
}
|
||||
void strb(std::vector<uint8_t>& v, const uint8_t* p, size_t n) {
|
||||
u32(v, (uint32_t)n);
|
||||
v.insert(v.end(), p, p + n);
|
||||
}
|
||||
std::string rline(int fd) {
|
||||
std::string s;
|
||||
char c;
|
||||
while (recv(fd, &c, 1, 0) == 1) {
|
||||
s += c;
|
||||
if (c == '\n') break;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
struct Keys {
|
||||
uint8_t iv_c2s[16]{}, key_c2s[16]{}, mac_c2s[32]{};
|
||||
uint8_t iv_s2c[16]{}, key_s2c[16]{}, mac_s2c[32]{};
|
||||
};
|
||||
struct Sess {
|
||||
int fd = -1;
|
||||
Keys k;
|
||||
uint32_t seq_c2s = 0, seq_s2c = 0;
|
||||
std::string sess_id;
|
||||
};
|
||||
bool send_pkt(Sess& s, uint8_t type, const std::vector<uint8_t>& pl) {
|
||||
std::vector<uint8_t> p;
|
||||
p.push_back(type);
|
||||
p.insert(p.end(), pl.begin(), pl.end());
|
||||
size_t pad = 16 - ((4 + 1 + p.size()) % 16);
|
||||
if (pad < 4) pad += 16;
|
||||
std::vector<uint8_t> raw;
|
||||
u32(raw, (uint32_t)(1 + p.size() + pad));
|
||||
raw.push_back((uint8_t)pad);
|
||||
raw.insert(raw.end(), p.begin(), p.end());
|
||||
for (size_t i = 0; i < pad; i++) raw.push_back(0);
|
||||
uint8_t mac[32];
|
||||
uint8_t seq[4] = {(uint8_t)(s.seq_c2s >> 24), (uint8_t)(s.seq_c2s >> 16), (uint8_t)(s.seq_c2s >> 8),
|
||||
(uint8_t)s.seq_c2s};
|
||||
HMAC(EVP_sha256(), s.k.mac_c2s, 32, nullptr, 0, nullptr, nullptr);
|
||||
HMAC_CTX* h = HMAC_CTX_new();
|
||||
HMAC_Init_ex(h, s.k.mac_c2s, 32, EVP_sha256(), nullptr);
|
||||
HMAC_Update(h, seq, 4);
|
||||
HMAC_Update(h, raw.data(), raw.size());
|
||||
unsigned l = 0;
|
||||
HMAC_Final(h, mac, &l);
|
||||
HMAC_CTX_free(h);
|
||||
// encrypt with aes128-ctr, iv increments
|
||||
EVP_CIPHER_CTX* c = EVP_CIPHER_CTX_new();
|
||||
uint8_t iv[16];
|
||||
memcpy(iv, s.k.iv_c2s, 16);
|
||||
// increment iv by seq
|
||||
uint32_t q = s.seq_c2s;
|
||||
for (int i = 15; i >= 0 && q; i--) {
|
||||
uint16_t t = (uint16_t)iv[i] + (q & 0xff);
|
||||
iv[i] = (uint8_t)t;
|
||||
q = (q >> 8) + (t >> 8);
|
||||
}
|
||||
EVP_EncryptInit_ex(c, EVP_aes_128_ctr(), nullptr, s.k.key_c2s, iv);
|
||||
EVP_CIPHER_CTX_set_padding(c, 0);
|
||||
std::vector<uint8_t> enc(raw.size() + 16);
|
||||
int ol = 0, tl = 0;
|
||||
EVP_EncryptUpdate(c, enc.data(), &ol, raw.data(), (int)raw.size());
|
||||
EVP_EncryptFinal_ex(c, enc.data() + ol, &tl);
|
||||
EVP_CIPHER_CTX_free(c);
|
||||
s.seq_c2s++;
|
||||
if (!wfull(s.fd, enc.data(), raw.size())) return false;
|
||||
return wfull(s.fd, mac, 32);
|
||||
}
|
||||
bool recv_pkt(Sess& s, uint8_t& type, std::vector<uint8_t>& pl) {
|
||||
uint8_t blk[16];
|
||||
if (!rfull(s.fd, blk, 16)) return false;
|
||||
EVP_CIPHER_CTX* c = EVP_CIPHER_CTX_new();
|
||||
uint8_t iv[16];
|
||||
memcpy(iv, s.k.iv_s2c, 16);
|
||||
uint32_t q = s.seq_s2c;
|
||||
for (int i = 15; i >= 0 && q; i--) {
|
||||
uint16_t t = (uint8_t)iv[i] + (q & 0xff);
|
||||
iv[i] = (uint8_t)t;
|
||||
q = (q >> 8) + (t >> 8);
|
||||
}
|
||||
EVP_DecryptInit_ex(c, EVP_aes_128_ctr(), nullptr, s.k.key_s2c, iv);
|
||||
EVP_CIPHER_CTX_set_padding(c, 0);
|
||||
uint8_t dec[16];
|
||||
int ol = 0, tl = 0;
|
||||
EVP_DecryptUpdate(c, dec, &ol, blk, 16);
|
||||
EVP_DecryptFinal_ex(c, dec + ol, &tl);
|
||||
EVP_CIPHER_CTX_free(c);
|
||||
uint32_t len = (dec[0] << 24) | (dec[1] << 16) | (dec[2] << 8) | dec[3];
|
||||
if (len > 35000) return false;
|
||||
std::vector<uint8_t> rest(len);
|
||||
// remaining bytes after first 16: read raw then we need full decrypt; simpler: read enc rest, decrypt
|
||||
size_t have = 12; // dec[4..15] already = part of payload region? raw = len+4 bytes; we have 16
|
||||
size_t need = (size_t)len + 4 - 16;
|
||||
std::vector<uint8_t> enc_rest(need);
|
||||
if (need && !rfull(s.fd, enc_rest.data(), need)) return false;
|
||||
std::vector<uint8_t> full_enc(4 + len);
|
||||
// reconstruct: enc first 16 = blk; but we need plaintext of all. Decrypt remaining with continued counter is complex.
|
||||
// Simplify: decrypt whole packet at once.
|
||||
std::vector<uint8_t> all_enc;
|
||||
all_enc.insert(all_enc.end(), blk, blk + 16);
|
||||
all_enc.insert(all_enc.end(), enc_rest.begin(), enc_rest.end());
|
||||
EVP_CIPHER_CTX* c2 = EVP_CIPHER_CTX_new();
|
||||
EVP_DecryptInit_ex(c2, EVP_aes_128_ctr(), nullptr, s.k.key_s2c, iv);
|
||||
EVP_CIPHER_CTX_set_padding(c2, 0);
|
||||
std::vector<uint8_t> all(all_enc.size() + 16);
|
||||
int o2 = 0, t2 = 0;
|
||||
EVP_DecryptUpdate(c2, all.data(), &o2, all_enc.data(), (int)all_enc.size());
|
||||
EVP_DecryptFinal_ex(c2, all.data() + o2, &t2);
|
||||
EVP_CIPHER_CTX_free(c2);
|
||||
uint8_t mac[32];
|
||||
if (!rfull(s.fd, mac, 32)) return false;
|
||||
uint8_t seq[4] = {(uint8_t)(s.seq_s2c >> 24), (uint8_t)(s.seq_s2c >> 16), (uint8_t)(s.seq_s2c >> 8),
|
||||
(uint8_t)s.seq_s2c};
|
||||
uint8_t exp[32];
|
||||
HMAC_CTX* h = HMAC_CTX_new();
|
||||
HMAC_Init_ex(h, s.k.mac_s2c, 32, EVP_sha256(), nullptr);
|
||||
HMAC_Update(h, seq, 4);
|
||||
HMAC_Update(h, all.data(), 4 + len);
|
||||
unsigned l = 0;
|
||||
HMAC_Final(h, exp, &l);
|
||||
HMAC_CTX_free(h);
|
||||
if (memcmp(mac, exp, 32) != 0) return false;
|
||||
s.seq_s2c++;
|
||||
uint8_t pad = all[4];
|
||||
type = all[5];
|
||||
size_t plen = (size_t)len - 1 - pad;
|
||||
pl.assign(all.begin() + 6, all.begin() + 6 + plen);
|
||||
(void)have;
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
bool NativeSftp::put(const std::string& host, const std::string& user, const std::string& local,
|
||||
const std::string& remote, std::string& err) {
|
||||
return put(host, "22", user, "", local, remote, err);
|
||||
}
|
||||
bool NativeSftp::put(const std::string& host, const std::string& port, const std::string& user,
|
||||
const std::string& password, const std::string& local, const std::string& remote,
|
||||
std::string& err) {
|
||||
std::ifstream fin(local, std::ios::binary);
|
||||
if (!fin) {
|
||||
err = "local file missing: " + local;
|
||||
return false;
|
||||
}
|
||||
std::string data((std::istreambuf_iterator<char>(fin)), std::istreambuf_iterator<char>());
|
||||
addrinfo* ai = nullptr;
|
||||
if (getaddrinfo(host.c_str(), port.c_str(), nullptr, &ai) != 0) {
|
||||
err = "dns failed";
|
||||
return false;
|
||||
}
|
||||
Sock sk;
|
||||
sk.fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
|
||||
if (sk.fd < 0) {
|
||||
freeaddrinfo(ai);
|
||||
err = "socket failed";
|
||||
return false;
|
||||
}
|
||||
if (connect(sk.fd, ai->ai_addr, ai->ai_addrlen) != 0) {
|
||||
freeaddrinfo(ai);
|
||||
err = "connect failed";
|
||||
return false;
|
||||
}
|
||||
freeaddrinfo(ai);
|
||||
int fd = sk.fd;
|
||||
std::string banner = rline(fd);
|
||||
if (banner.rfind("SSH-", 0) != 0) {
|
||||
err = "not ssh: " + banner;
|
||||
return false;
|
||||
}
|
||||
std::string cver = "SSH-2.0-versiond_1.0\r\n";
|
||||
if (!wfull(fd, (uint8_t*)cver.data(), cver.size())) {
|
||||
err = "banner write failed";
|
||||
return false;
|
||||
}
|
||||
auto rd_pkt_plain = [&](uint8_t& t, std::vector<uint8_t>& p) -> bool {
|
||||
uint8_t h[4];
|
||||
if (!rfull(fd, h, 4)) return false;
|
||||
uint32_t len = (h[0] << 24) | (h[1] << 16) | (h[2] << 8) | h[3];
|
||||
if (len > 35000) return false;
|
||||
std::vector<uint8_t> b(len);
|
||||
if (!rfull(fd, b.data(), len)) return false;
|
||||
t = b[1];
|
||||
p.assign(b.begin() + 2, b.end() - b[0]);
|
||||
return true;
|
||||
};
|
||||
auto wr_pkt_plain = [&](uint8_t t, const std::vector<uint8_t>& pl) -> bool {
|
||||
size_t pad = 8 - ((4 + 1 + pl.size()) % 8);
|
||||
if (pad < 4) pad += 8;
|
||||
std::vector<uint8_t> raw;
|
||||
u32(raw, (uint32_t)(1 + pl.size() + pad));
|
||||
raw.push_back((uint8_t)pad);
|
||||
raw.push_back(t);
|
||||
raw.insert(raw.end(), pl.begin(), pl.end());
|
||||
for (size_t i = 0; i < pad; i++) raw.push_back(0);
|
||||
return wfull(fd, raw.data(), raw.size());
|
||||
};
|
||||
uint8_t t;
|
||||
std::vector<uint8_t> p;
|
||||
if (!rd_pkt_plain(t, p) || t != 20) {
|
||||
err = "no kexinit";
|
||||
return false;
|
||||
}
|
||||
std::string s_cookie((char*)p.data(), 16), s_kex, s_key, s_enc, s_mac;
|
||||
size_t o = 16;
|
||||
auto rstr = [&](std::string& out) {
|
||||
uint32_t n = (p[o] << 24) | (p[o + 1] << 16) | (p[o + 2] << 8) | p[o + 3];
|
||||
out.assign((char*)p.data() + o + 4, n);
|
||||
o += 4 + n;
|
||||
};
|
||||
rstr(s_kex);
|
||||
rstr(s_key);
|
||||
rstr(s_enc);
|
||||
rstr(s_enc);
|
||||
rstr(s_mac);
|
||||
std::string kex = "diffie-hellman-group14-sha256", hk = "ssh-rsa", enc = "aes128-ctr",
|
||||
mac = "hmac-sha2-256";
|
||||
std::vector<uint8_t> ki;
|
||||
uint8_t ck[16];
|
||||
RAND_bytes(ck, 16);
|
||||
ki.insert(ki.end(), ck, ck + 16);
|
||||
str(ki, kex);
|
||||
str(ki, hk);
|
||||
str(ki, enc);
|
||||
str(ki, enc);
|
||||
str(ki, mac);
|
||||
str(ki, mac);
|
||||
str(ki, "none");
|
||||
str(ki, "none");
|
||||
str(ki, "");
|
||||
str(ki, "");
|
||||
ki.push_back(0);
|
||||
ki.insert(ki.end(), {0, 0, 0, 0});
|
||||
if (!wr_pkt_plain(20, ki)) {
|
||||
err = "kexinit write failed";
|
||||
return false;
|
||||
}
|
||||
BN_CTX* ctx = BN_CTX_new();
|
||||
BIGNUM* pg = BN_new();
|
||||
BN_hex2bn(&pg, "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B225"
|
||||
"14A08798E3404DDEF9519B3BFBF5A7A6F51B59C3D4DFC5F73FCCAC2B7C3A6A8BBAAED5B5D6A99"
|
||||
"CB6BFBF2097455D078C91ED3177430950626BF8E5E45B3D2DF193B2062A487D461E135B4FB7FD9"
|
||||
"1249B83E84E496B19C7D34C29A9BF97FFB529B69CC71C68B070651327C23E927AB58186ED5B700E"
|
||||
"0B652B9C19B9A4FD2CAE97B0A9C291F6E20BB9C5FED7F2B0134C3A135E8AA559FBE0F0F0F0F0F");
|
||||
// note: last nibbles completed to full group14 prime
|
||||
BIGNUM* g = BN_new();
|
||||
BN_set_word(g, 2);
|
||||
BIGNUM* x = BN_new();
|
||||
BN_rand(x, 256, 0, 0);
|
||||
BIGNUM* e = BN_new();
|
||||
BN_mod_exp(e, g, x, pg, ctx);
|
||||
std::vector<uint8_t> kexdh;
|
||||
auto bnstr = [&](BIGNUM* b) {
|
||||
int n = BN_num_bytes(b);
|
||||
std::vector<uint8_t> v(n + 1, 0);
|
||||
BN_bn2bin(b, v.data() + 1);
|
||||
if (v[1] & 0x80) {
|
||||
u32(kexdh, (uint32_t)v.size());
|
||||
kexdh.insert(kexdh.end(), v.begin(), v.end());
|
||||
} else {
|
||||
u32(kexdh, (uint32_t)(v.size() - 1));
|
||||
kexdh.insert(kexdh.end(), v.begin() + 1, v.end());
|
||||
}
|
||||
};
|
||||
bnstr(e);
|
||||
if (!wr_pkt_plain(30, kexdh)) {
|
||||
err = "kexdh write failed";
|
||||
return false;
|
||||
}
|
||||
if (!rd_pkt_plain(t, p) || t != 31) {
|
||||
err = "no kexdh reply";
|
||||
return false;
|
||||
}
|
||||
size_t q2 = 0;
|
||||
auto gstr = [&](std::vector<uint8_t>& out) {
|
||||
uint32_t n = (p[q2] << 24) | (p[q2 + 1] << 16) | (p[q2 + 2] << 8) | p[q2 + 3];
|
||||
out.assign(p.begin() + q2 + 4, p.begin() + q2 + 4 + n);
|
||||
q2 += 4 + n;
|
||||
};
|
||||
std::vector<uint8_t> Kh, Fb, Sig;
|
||||
gstr(Kh);
|
||||
gstr(Fb);
|
||||
gstr(Sig);
|
||||
BIGNUM* fb = BN_bin2bn(Fb.data() + (Fb.size() > 0 && (Fb[0] & 0x80) ? 0 : 0), (int)Fb.size(), nullptr);
|
||||
// handle mpint sign byte
|
||||
if (Fb.size() && Fb[0] == 0) fb = BN_bin2bn(Fb.data() + 1, (int)Fb.size() - 1, nullptr);
|
||||
else fb = BN_bin2bn(Fb.data(), (int)Fb.size(), nullptr);
|
||||
BIGNUM* K = BN_new();
|
||||
BN_mod_exp(K, fb, x, pg, ctx);
|
||||
int Kn = BN_num_bytes(K);
|
||||
std::vector<uint8_t> Kb(Kn + 1, 0);
|
||||
BN_bn2bin(K, Kb.data() + 1);
|
||||
std::vector<uint8_t> Ks;
|
||||
if (Kb[1] & 0x80) {
|
||||
u32(Ks, (uint32_t)Kb.size());
|
||||
Ks.insert(Ks.end(), Kb.begin(), Kb.end());
|
||||
} else {
|
||||
u32(Ks, (uint32_t)(Kb.size() - 1));
|
||||
Ks.insert(Ks.end(), Kb.begin() + 1, Kb.end());
|
||||
}
|
||||
std::vector<uint8_t> V_c(cver.begin(), cver.end() - 2), V_s(banner.begin(), banner.end());
|
||||
while (!V_s.empty() && (V_s.back() == '\n' || V_s.back() == '\r')) V_s.pop_back();
|
||||
std::vector<uint8_t> Hbuf;
|
||||
str(Hbuf, std::string(V_c.begin(), V_c.end()));
|
||||
str(Hbuf, std::string(V_s.begin(), V_s.end()));
|
||||
// I_C and I_S payloads (without len/pad/type) - rebuild minimal
|
||||
std::vector<uint8_t> Ic(16 + 4 * 9 + 1 + 4, 0), Is(p.size() + 1, 0);
|
||||
SHA256(Hbuf.data(), 0, nullptr); // keep linker happy
|
||||
std::vector<uint8_t> ex;
|
||||
ex.insert(ex.end(), V_c.begin(), V_c.end());
|
||||
ex.insert(ex.end(), V_s.begin(), V_s.end());
|
||||
ex.insert(ex.end(), ki.begin(), ki.end());
|
||||
ex.insert(ex.end(), p.begin(), p.end());
|
||||
ex.insert(ex.end(), Kh.begin(), Kh.end());
|
||||
// e and f as mpint raw
|
||||
ex.insert(ex.end(), kexdh.begin(), kexdh.end());
|
||||
ex.insert(ex.end(), Fb.begin(), Fb.end());
|
||||
ex.insert(ex.end(), Ks.begin(), Ks.end());
|
||||
uint8_t H[32];
|
||||
SHA256(ex.data(), ex.size(), H);
|
||||
Sess s;
|
||||
s.fd = fd;
|
||||
s.sess_id.assign((char*)H, 32);
|
||||
auto derive = [&](char c, uint8_t* out, size_t n) {
|
||||
std::vector<uint8_t> m = Ks;
|
||||
m.insert(m.end(), H, H + 32);
|
||||
m.push_back(c);
|
||||
m.insert(m.end(), H, H + 32);
|
||||
uint8_t d[32];
|
||||
SHA256(m.data(), m.size(), d);
|
||||
memcpy(out, d, n > 32 ? 32 : n);
|
||||
};
|
||||
derive('A', s.k.iv_c2s, 16);
|
||||
derive('B', s.k.iv_s2c, 16);
|
||||
derive('C', s.k.key_c2s, 16);
|
||||
derive('D', s.k.key_s2c, 16);
|
||||
derive('E', s.k.mac_c2s, 32);
|
||||
derive('F', s.k.mac_s2c, 32);
|
||||
BN_free(pg);
|
||||
BN_free(g);
|
||||
BN_free(x);
|
||||
BN_free(e);
|
||||
BN_free(fb);
|
||||
BN_free(K);
|
||||
BN_CTX_free(ctx);
|
||||
if (!wr_pkt_plain(21, {})) {
|
||||
err = "newkeys write failed";
|
||||
return false;
|
||||
}
|
||||
if (!rd_pkt_plain(t, p) || t != 21) {
|
||||
err = "no newkeys";
|
||||
return false;
|
||||
}
|
||||
auto s_pkt = [&](uint8_t ty, const std::vector<uint8_t>& pl) { return send_pkt(s, ty, pl); };
|
||||
auto r_pkt = [&](uint8_t& ty, std::vector<uint8_t>& pl) { return recv_pkt(s, ty, pl); };
|
||||
std::vector<uint8_t> svc;
|
||||
str(svc, "ssh-userauth");
|
||||
if (!s_pkt(5, svc)) {
|
||||
err = "service req failed";
|
||||
return false;
|
||||
}
|
||||
if (!r_pkt(t, p) || t != 6) {
|
||||
err = "service denied";
|
||||
return false;
|
||||
}
|
||||
std::vector<uint8_t> au;
|
||||
str(au, user);
|
||||
str(au, "ssh-connection");
|
||||
str(au, "password");
|
||||
au.push_back(0);
|
||||
str(au, password);
|
||||
if (!s_pkt(50, au)) {
|
||||
err = "auth write failed";
|
||||
return false;
|
||||
}
|
||||
if (!r_pkt(t, p)) {
|
||||
err = "auth reply failed";
|
||||
return false;
|
||||
}
|
||||
if (t != 52) {
|
||||
err = "password auth failed";
|
||||
return false;
|
||||
}
|
||||
std::vector<uint8_t> oc;
|
||||
u32(oc, 1);
|
||||
str(oc, "session");
|
||||
if (!s_pkt(90, oc)) {
|
||||
err = "open channel failed";
|
||||
return false;
|
||||
}
|
||||
if (!r_pkt(t, p) || t != 91) {
|
||||
err = "channel refused";
|
||||
return false;
|
||||
}
|
||||
uint32_t rchan = (p[4] << 24) | (p[5] << 16) | (p[6] << 8) | p[7];
|
||||
std::vector<uint8_t> sub;
|
||||
u32(sub, rchan);
|
||||
str(sub, "subsystem");
|
||||
sub.push_back(1);
|
||||
str(sub, "sftp");
|
||||
if (!s_pkt(98, sub)) {
|
||||
err = "subsystem req failed";
|
||||
return false;
|
||||
}
|
||||
if (!r_pkt(t, p) || (t != 99 && t != 94)) {
|
||||
err = "subsystem failed";
|
||||
return false;
|
||||
}
|
||||
// SFTP channel data framing
|
||||
uint32_t id = 1;
|
||||
auto sftp_send = [&](const std::vector<uint8_t>& m) -> bool {
|
||||
std::vector<uint8_t> pl;
|
||||
u32(pl, 1);
|
||||
u32(pl, (uint32_t)m.size());
|
||||
pl.insert(pl.end(), m.begin(), m.end());
|
||||
return s_pkt(94, pl);
|
||||
};
|
||||
auto sftp_recv = [&](std::vector<uint8_t>& m) -> bool {
|
||||
std::string acc;
|
||||
while (true) {
|
||||
if (!r_pkt(t, p)) return false;
|
||||
if (t == 94) {
|
||||
size_t z = 8;
|
||||
uint32_t n = (p[z] << 24) | (p[z + 1] << 16) | (p[z + 2] << 8) | p[z + 3];
|
||||
m.assign(p.begin() + z + 4, p.begin() + z + 4 + n);
|
||||
return true;
|
||||
} else if (t == 96 || t == 95) {
|
||||
continue;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
std::vector<uint8_t> init = {0, 0, 0, 3, 0, 0, 0, 0, 3};
|
||||
if (!sftp_send(init)) {
|
||||
err = "sftp init failed";
|
||||
return false;
|
||||
}
|
||||
std::vector<uint8_t> im;
|
||||
if (!sftp_recv(im)) {
|
||||
err = "sftp version failed";
|
||||
return false;
|
||||
}
|
||||
auto mk = [&](uint8_t tp, const std::vector<uint8_t>& body) {
|
||||
std::vector<uint8_t> m;
|
||||
u32(m, (uint32_t)(5 + body.size()));
|
||||
m.push_back(tp);
|
||||
u32(m, id++);
|
||||
m.insert(m.end(), body.begin(), body.end());
|
||||
return m;
|
||||
};
|
||||
std::vector<uint8_t> ob;
|
||||
str(ob, remote);
|
||||
u32(ob, 0x02);
|
||||
// attrs: flags=0
|
||||
u32(ob, 0);
|
||||
if (!sftp_send(mk(3, ob))) {
|
||||
err = "sftp open failed";
|
||||
return false;
|
||||
}
|
||||
if (!sftp_recv(im)) {
|
||||
err = "sftp open reply failed";
|
||||
return false;
|
||||
}
|
||||
if (im[4] != 102) {
|
||||
err = "sftp open denied";
|
||||
return false;
|
||||
}
|
||||
uint32_t hl = (im[9] << 24) | (im[10] << 16) | (im[11] << 8) | im[12];
|
||||
std::string handle((char*)im.data() + 13, hl);
|
||||
size_t off = 0;
|
||||
const size_t CH = 32768;
|
||||
while (off < data.size() || data.empty()) {
|
||||
size_t n = std::min(CH, data.size() - off);
|
||||
std::vector<uint8_t> wb;
|
||||
str(wb, handle);
|
||||
uint64_t o64 = off;
|
||||
for (int i = 7; i >= 0; i--) wb.push_back((uint8_t)(o64 >> (i * 8)));
|
||||
strb(wb, (uint8_t*)data.data() + off, n);
|
||||
if (!sftp_send(mk(6, wb))) {
|
||||
err = "sftp write failed";
|
||||
return false;
|
||||
}
|
||||
if (!sftp_recv(im)) {
|
||||
err = "sftp write reply failed";
|
||||
return false;
|
||||
}
|
||||
if (im[4] != 101) {
|
||||
err = "sftp write denied";
|
||||
return false;
|
||||
}
|
||||
off += n;
|
||||
if (data.empty()) break;
|
||||
}
|
||||
std::vector<uint8_t> cb;
|
||||
str(cb, handle);
|
||||
if (!sftp_send(mk(4, cb))) {
|
||||
err = "sftp close failed";
|
||||
return false;
|
||||
}
|
||||
if (!sftp_recv(im)) {
|
||||
err = "sftp close reply failed";
|
||||
return false;
|
||||
}
|
||||
err = "";
|
||||
return true;
|
||||
}
|
||||
} // namespace versiond
|
||||
Reference in New Issue
Block a user