mirror of
https://github.com/NixOS/nix.git
synced 2025-12-18 15:01:08 +01:00
Move /src to /subprojects
This will facilitate breaking up Nix into multiple packages for each component with Meson.
This commit is contained in:
parent
4db9487823
commit
84e2963f8e
737 changed files with 504 additions and 505 deletions
234
subprojects/libstore/ssh.cc
Normal file
234
subprojects/libstore/ssh.cc
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
#include "ssh.hh"
|
||||
#include "finally.hh"
|
||||
#include "current-process.hh"
|
||||
#include "environment-variables.hh"
|
||||
#include "util.hh"
|
||||
#include "exec.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
static std::string parsePublicHostKey(std::string_view host, std::string_view sshPublicHostKey)
|
||||
{
|
||||
try {
|
||||
return base64Decode(sshPublicHostKey);
|
||||
} catch (Error & e) {
|
||||
e.addTrace({}, "while decoding ssh public host key for host '%s'", host);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
SSHMaster::SSHMaster(
|
||||
std::string_view host,
|
||||
std::string_view keyFile,
|
||||
std::string_view sshPublicHostKey,
|
||||
bool useMaster, bool compress, Descriptor logFD)
|
||||
: host(host)
|
||||
, fakeSSH(host == "localhost")
|
||||
, keyFile(keyFile)
|
||||
, sshPublicHostKey(parsePublicHostKey(host, sshPublicHostKey))
|
||||
, useMaster(useMaster && !fakeSSH)
|
||||
, compress(compress)
|
||||
, logFD(logFD)
|
||||
{
|
||||
if (host == "" || hasPrefix(host, "-"))
|
||||
throw Error("invalid SSH host name '%s'", host);
|
||||
|
||||
auto state(state_.lock());
|
||||
state->tmpDir = std::make_unique<AutoDelete>(createTempDir("", "nix", true, true, 0700));
|
||||
}
|
||||
|
||||
void SSHMaster::addCommonSSHOpts(Strings & args)
|
||||
{
|
||||
auto state(state_.lock());
|
||||
|
||||
for (auto & i : tokenizeString<Strings>(getEnv("NIX_SSHOPTS").value_or("")))
|
||||
args.push_back(i);
|
||||
if (!keyFile.empty())
|
||||
args.insert(args.end(), {"-i", keyFile});
|
||||
if (!sshPublicHostKey.empty()) {
|
||||
std::filesystem::path fileName = state->tmpDir->path() / "host-key";
|
||||
auto p = host.rfind("@");
|
||||
std::string thost = p != std::string::npos ? std::string(host, p + 1) : host;
|
||||
writeFile(fileName.string(), thost + " " + sshPublicHostKey + "\n");
|
||||
args.insert(args.end(), {"-oUserKnownHostsFile=" + fileName.string()});
|
||||
}
|
||||
if (compress)
|
||||
args.push_back("-C");
|
||||
|
||||
// We use this to make ssh signal back to us that the connection is established.
|
||||
// It really does run locally; see createSSHEnv which sets up SHELL to make
|
||||
// it launch more reliably. The local command runs synchronously, so presumably
|
||||
// the remote session won't be garbled if the local command is slow.
|
||||
args.push_back("-oPermitLocalCommand=yes");
|
||||
args.push_back("-oLocalCommand=echo started");
|
||||
}
|
||||
|
||||
bool SSHMaster::isMasterRunning() {
|
||||
Strings args = {"-O", "check", host};
|
||||
addCommonSSHOpts(args);
|
||||
|
||||
auto res = runProgram(RunOptions {.program = "ssh", .args = args, .mergeStderrToStdout = true});
|
||||
return res.first == 0;
|
||||
}
|
||||
|
||||
Strings createSSHEnv()
|
||||
{
|
||||
// Copy the environment and set SHELL=/bin/sh
|
||||
std::map<std::string, std::string> env = getEnv();
|
||||
|
||||
// SSH will invoke the "user" shell for -oLocalCommand, but that means
|
||||
// $SHELL. To keep things simple and avoid potential issues with other
|
||||
// shells, we set it to /bin/sh.
|
||||
// Technically, we don't need that, and we could reinvoke ourselves to print
|
||||
// "started". Self-reinvocation is tricky with library consumers, but mostly
|
||||
// solved; refer to the development history of nixExePath in libstore/globals.cc.
|
||||
env.insert_or_assign("SHELL", "/bin/sh");
|
||||
|
||||
Strings r;
|
||||
for (auto & [k, v] : env) {
|
||||
r.push_back(k + "=" + v);
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
std::unique_ptr<SSHMaster::Connection> SSHMaster::startCommand(
|
||||
Strings && command, Strings && extraSshArgs)
|
||||
{
|
||||
#ifdef _WIN32 // TODO re-enable on Windows, once we can start processes.
|
||||
throw UnimplementedError("cannot yet SSH on windows because spawning processes is not yet implemented");
|
||||
#else
|
||||
Path socketPath = startMaster();
|
||||
|
||||
Pipe in, out;
|
||||
in.create();
|
||||
out.create();
|
||||
|
||||
auto conn = std::make_unique<Connection>();
|
||||
ProcessOptions options;
|
||||
options.dieWithParent = false;
|
||||
|
||||
if (!fakeSSH && !useMaster) {
|
||||
logger->pause();
|
||||
}
|
||||
Finally cleanup = [&]() { logger->resume(); };
|
||||
|
||||
conn->sshPid = startProcess([&]() {
|
||||
restoreProcessContext();
|
||||
|
||||
close(in.writeSide.get());
|
||||
close(out.readSide.get());
|
||||
|
||||
if (dup2(in.readSide.get(), STDIN_FILENO) == -1)
|
||||
throw SysError("duping over stdin");
|
||||
if (dup2(out.writeSide.get(), STDOUT_FILENO) == -1)
|
||||
throw SysError("duping over stdout");
|
||||
if (logFD != -1 && dup2(logFD, STDERR_FILENO) == -1)
|
||||
throw SysError("duping over stderr");
|
||||
|
||||
Strings args;
|
||||
|
||||
if (!fakeSSH) {
|
||||
args = { "ssh", host.c_str(), "-x" };
|
||||
addCommonSSHOpts(args);
|
||||
if (socketPath != "")
|
||||
args.insert(args.end(), {"-S", socketPath});
|
||||
if (verbosity >= lvlChatty)
|
||||
args.push_back("-v");
|
||||
args.splice(args.end(), std::move(extraSshArgs));
|
||||
args.push_back("--");
|
||||
}
|
||||
|
||||
args.splice(args.end(), std::move(command));
|
||||
auto env = createSSHEnv();
|
||||
nix::execvpe(args.begin()->c_str(), stringsToCharPtrs(args).data(), stringsToCharPtrs(env).data());
|
||||
|
||||
// could not exec ssh/bash
|
||||
throw SysError("unable to execute '%s'", args.front());
|
||||
}, options);
|
||||
|
||||
|
||||
in.readSide = INVALID_DESCRIPTOR;
|
||||
out.writeSide = INVALID_DESCRIPTOR;
|
||||
|
||||
// Wait for the SSH connection to be established,
|
||||
// So that we don't overwrite the password prompt with our progress bar.
|
||||
if (!fakeSSH && !useMaster && !isMasterRunning()) {
|
||||
std::string reply;
|
||||
try {
|
||||
reply = readLine(out.readSide.get());
|
||||
} catch (EndOfFile & e) { }
|
||||
|
||||
if (reply != "started") {
|
||||
printTalkative("SSH stdout first line: %s", reply);
|
||||
throw Error("failed to start SSH connection to '%s'", host);
|
||||
}
|
||||
}
|
||||
|
||||
conn->out = std::move(out.readSide);
|
||||
conn->in = std::move(in.writeSide);
|
||||
|
||||
return conn;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef _WIN32 // TODO re-enable on Windows, once we can start processes.
|
||||
|
||||
Path SSHMaster::startMaster()
|
||||
{
|
||||
if (!useMaster) return "";
|
||||
|
||||
auto state(state_.lock());
|
||||
|
||||
if (state->sshMaster != INVALID_DESCRIPTOR) return state->socketPath;
|
||||
|
||||
state->socketPath = (Path) *state->tmpDir + "/ssh.sock";
|
||||
|
||||
Pipe out;
|
||||
out.create();
|
||||
|
||||
ProcessOptions options;
|
||||
options.dieWithParent = false;
|
||||
|
||||
logger->pause();
|
||||
Finally cleanup = [&]() { logger->resume(); };
|
||||
|
||||
if (isMasterRunning())
|
||||
return state->socketPath;
|
||||
|
||||
state->sshMaster = startProcess([&]() {
|
||||
restoreProcessContext();
|
||||
|
||||
close(out.readSide.get());
|
||||
|
||||
if (dup2(out.writeSide.get(), STDOUT_FILENO) == -1)
|
||||
throw SysError("duping over stdout");
|
||||
|
||||
Strings args = { "ssh", host.c_str(), "-M", "-N", "-S", state->socketPath };
|
||||
if (verbosity >= lvlChatty)
|
||||
args.push_back("-v");
|
||||
addCommonSSHOpts(args);
|
||||
auto env = createSSHEnv();
|
||||
nix::execvpe(args.begin()->c_str(), stringsToCharPtrs(args).data(), stringsToCharPtrs(env).data());
|
||||
|
||||
throw SysError("unable to execute '%s'", args.front());
|
||||
}, options);
|
||||
|
||||
out.writeSide = INVALID_DESCRIPTOR;
|
||||
|
||||
std::string reply;
|
||||
try {
|
||||
reply = readLine(out.readSide.get());
|
||||
} catch (EndOfFile & e) { }
|
||||
|
||||
if (reply != "started") {
|
||||
printTalkative("SSH master stdout first line: %s", reply);
|
||||
throw Error("failed to start SSH master connection to '%s'", host);
|
||||
}
|
||||
|
||||
return state->socketPath;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue