#include "libstore/ssh.hh" #include <utility> #include <absl/strings/match.h> #include <absl/strings/str_split.h> namespace nix { SSHMaster::SSHMaster(const std::string& host, std::string keyFile, bool useMaster, bool compress, int logFD) : host(host), fakeSSH(host == "localhost"), keyFile(std::move(keyFile)), useMaster(useMaster && !fakeSSH), compress(compress), logFD(logFD) { if (host.empty() || absl::StartsWith(host, "-")) { throw Error("invalid SSH host name '%s'", host); } } void SSHMaster::addCommonSSHOpts(Strings& args) { for (auto& i : absl::StrSplit(getEnv("NIX_SSHOPTS"), absl::ByAnyChar(" \t\n\r"), absl::SkipEmpty())) { args.push_back(std::string(i)); } if (!keyFile.empty()) { args.insert(args.end(), {"-i", keyFile}); } if (compress) { args.push_back("-C"); } } std::unique_ptr<SSHMaster::Connection> SSHMaster::startCommand( const std::string& command) { Path socketPath = startMaster(); Pipe in; Pipe out; in.create(); out.create(); auto conn = std::make_unique<Connection>(); ProcessOptions options; options.dieWithParent = false; conn->sshPid = startProcess( [&]() { restoreSignals(); 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 = {"bash", "-c"}; } else { args = {"ssh", host, "-x", "-a"}; addCommonSSHOpts(args); if (!socketPath.empty()) { args.insert(args.end(), {"-S", socketPath}); } // TODO(tazjin): Abseil verbosity flag /*if (verbosity >= lvlChatty) { args.push_back("-v"); }*/ } args.push_back(command); execvp(args.begin()->c_str(), stringsToCharPtrs(args).data()); // could not exec ssh/bash throw SysError("unable to execute '%s'", args.front()); }, options); in.readSide = -1; out.writeSide = -1; conn->out = std::move(out.readSide); conn->in = std::move(in.writeSide); return conn; } Path SSHMaster::startMaster() { if (!useMaster) { return ""; } auto state(state_.lock()); if (state->sshMaster != -1) { return state->socketPath; } state->tmpDir = std::make_unique<AutoDelete>(createTempDir("", "nix", true, true, 0700)); state->socketPath = (Path)*state->tmpDir + "/ssh.sock"; Pipe out; out.create(); ProcessOptions options; options.dieWithParent = false; state->sshMaster = startProcess( [&]() { restoreSignals(); close(out.readSide.get()); if (dup2(out.writeSide.get(), STDOUT_FILENO) == -1) { throw SysError("duping over stdout"); } Strings args = {"ssh", host, "-M", "-N", "-S", state->socketPath, "-o", "LocalCommand=echo started", "-o", "PermitLocalCommand=yes"}; // if (verbosity >= lvlChatty) { args.push_back("-v"); } addCommonSSHOpts(args); execvp(args.begin()->c_str(), stringsToCharPtrs(args).data()); throw SysError("unable to execute '%s'", args.front()); }, options); out.writeSide = -1; std::string reply; try { reply = readLine(out.readSide.get()); } catch (EndOfFile& e) { } if (reply != "started") { throw Error("failed to start SSH master connection to '%s'", host); } return state->socketPath; } } // namespace nix