Skip to content

Commit

Permalink
Dynamically generate profiles from hosts in OpenSSH config files
Browse files Browse the repository at this point in the history
This PR adds a new `SshHostGenerator` inbox dynamic profile generator. When run, it looks for an install of our [Win32-OpenSSH](https://github.com/PowerShell/Win32-OpenSSH) client app `ssh.exe` in all of the (official) places it gets installed. If the exe is found, the generator then looks for and parses both the user and system OpenSSH config files for valid SSH hosts. Each host is then converted into a profiles to call `ssh.exe` and connect to those hosts.

Closes microsoft#9031
  • Loading branch information
jonthysell committed Sep 20, 2022
1 parent 08096b2 commit 3e851a9
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "PowershellCoreProfileGenerator.h"
#include "VisualStudioGenerator.h"
#include "WslDistroGenerator.h"
#include "SshHostGenerator.h"

// The following files are generated at build time into the "Generated Files" directory.
// defaults(-universal).h is a file containing the default json settings in a std::string_view.
Expand Down Expand Up @@ -148,6 +149,7 @@ void SettingsLoader::GenerateProfiles()
_executeGenerator(WslDistroGenerator{});
_executeGenerator(AzureCloudShellGenerator{});
_executeGenerator(VisualStudioGenerator{});
_executeGenerator(SshHostGenerator{});
}

// A new settings.json gets a special treatment:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
<ClInclude Include="VsDevShellGenerator.h" />
<ClInclude Include="VsSetupConfiguration.h" />
<ClInclude Include="WslDistroGenerator.h" />
<ClInclude Include="SshHostGenerator.h" />
</ItemGroup>
<!-- ========================= Cpp Files ======================== -->
<ItemGroup>
Expand Down Expand Up @@ -165,6 +166,7 @@
<ClCompile Include="VsDevShellGenerator.cpp" />
<ClCompile Include="VsSetupConfiguration.cpp" />
<ClCompile Include="WslDistroGenerator.cpp" />
<ClCompile Include="SshHostGenerator.cpp" />
<!-- You _NEED_ to include this file and the jsoncpp IncludePath (below) if
you want to use jsoncpp -->
<ClCompile Include="$(OpenConsoleDir)\dep\jsoncpp\jsoncpp.cpp">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
<ClCompile Include="WslDistroGenerator.cpp">
<Filter>profileGeneration</Filter>
</ClCompile>
<ClCompile Include="SshHostGenerator.cpp">
<Filter>profileGeneration</Filter>
</ClCompile>
<ClCompile Include="CascadiaSettings.cpp" />
<ClCompile Include="CascadiaSettingsSerialization.cpp" />
<ClCompile Include="GlobalAppSettings.cpp" />
Expand Down Expand Up @@ -61,6 +64,9 @@
<ClInclude Include="WslDistroGenerator.h">
<Filter>profileGeneration</Filter>
</ClInclude>
<ClInclude Include="SshHostGenerator.h">
<Filter>profileGeneration</Filter>
</ClInclude>
<ClInclude Include="CascadiaSettings.h" />
<ClInclude Include="GlobalAppSettings.h" />
<ClInclude Include="TerminalSettingsSerializationHelpers.h" />
Expand Down
114 changes: 114 additions & 0 deletions src/cascadia/TerminalSettingsModel/SshHostGenerator.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

#include "pch.h"

#include "SshHostGenerator.h"
#include "../../inc/DefaultSettings.h"

#include "DynamicProfileUtils.h"

static constexpr std::wstring_view SshHostGeneratorNamespace{ L"Windows.Terminal.SSH" };

static constexpr const wchar_t* PROFILE_TITLE_PREFIX = L"SSH - ";

static constexpr const wchar_t* SSH_EXE_PATH1 = L"%SystemRoot%\\System32\\OpenSSH\\ssh.exe";
static constexpr const wchar_t* SSH_EXE_PATH2 = L"%ProgramFiles%\\OpenSSH\\ssh.exe";
static constexpr const wchar_t* SSH_EXE_PATH3 = L"%ProgramFiles(x86)%\\OpenSSH\\ssh.exe";

static constexpr const wchar_t* SSH_SYSTEMCONFIG_PATH = L"%ProgramData%\\ssh\\ssh_config";
static constexpr const wchar_t* SSH_USERCONFIG_PATH = L"%UserProfile%\\.ssh\\config";

static constexpr std::wstring_view SSH_HOST_PREFIX{ L"Host " };

using namespace ::Microsoft::Terminal::Settings::Model;
using namespace winrt::Microsoft::Terminal::Settings::Model;

std::wstring_view SshHostGenerator::GetNamespace() const noexcept
{
return SshHostGeneratorNamespace;
}

static bool tryFindSshExePath(std::wstring& sshExePath) noexcept
{
try
{
// OpenSSH is installed under System32 when installed via Optional Features
if (std::filesystem::exists(wil::ExpandEnvironmentStringsW<std::wstring>(SSH_EXE_PATH1)))
{
sshExePath = SSH_EXE_PATH1;
return true;
}

// OpenSSH (x86/x64) is installed under Program Files when installed via MSI
if (std::filesystem::exists(wil::ExpandEnvironmentStringsW<std::wstring>(SSH_EXE_PATH2)))
{
sshExePath = SSH_EXE_PATH2;
return true;
}

// OpenSSH (x86) is installed under Program Files x86 when installed via MSI on x64 machine
if (std::filesystem::exists(wil::ExpandEnvironmentStringsW<std::wstring>(SSH_EXE_PATH3)))
{
sshExePath = SSH_EXE_PATH3;
return true;
}
}
CATCH_LOG();

return false;
}

static winrt::com_ptr<implementation::Profile> makeProfile(const std::wstring& sshExePath, const std::wstring& hostName)
{
const auto profile{ CreateDynamicProfile(PROFILE_TITLE_PREFIX + hostName) };

profile->Commandline(winrt::hstring{ L"\"" + sshExePath + L"\" " + hostName });

return profile;
}

static void tryGetHostNamesFromConfigFile(const std::wstring configPath, std::vector<std::wstring>& hostNames) noexcept
{
try
{
const std::filesystem::path resolvedConfigPath{ wil::ExpandEnvironmentStringsW<std::wstring>(configPath.c_str()) };
if (std::filesystem::exists(resolvedConfigPath))
{
std::wifstream inputStream(resolvedConfigPath);

std::wstring line;
while (std::getline(inputStream, line))
{
if (line.starts_with(SSH_HOST_PREFIX))
{
hostNames.emplace_back(line.substr(SSH_HOST_PREFIX.length()));
}
}
}
}
CATCH_LOG();
}

// Method Description:
// - Generate a list of profiles for each detected OpenSSH host.
// Arguments:
// - <none>
// Return Value:
// - <A list of SSH host profiles.>
void SshHostGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const
{
std::wstring sshExePath;
if (tryFindSshExePath(sshExePath))
{
std::vector<std::wstring> hostNames;

tryGetHostNamesFromConfigFile(SSH_SYSTEMCONFIG_PATH, hostNames);
tryGetHostNamesFromConfigFile(SSH_USERCONFIG_PATH, hostNames);

for (const auto& hostName : hostNames)
{
profiles.emplace_back(makeProfile(sshExePath, hostName));
}
}
}
29 changes: 29 additions & 0 deletions src/cascadia/TerminalSettingsModel/SshHostGenerator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- SshHostGenerator
Abstract:
- This is the dynamic profile generator for SSH connections. Enumerates all the
SSH hosts to create profiles for them.
Author(s):
- Jon Thysell - September 2022
--*/

#pragma once

#include "IDynamicProfileGenerator.h"

namespace winrt::Microsoft::Terminal::Settings::Model
{
class SshHostGenerator final : public IDynamicProfileGenerator
{
public:
std::wstring_view GetNamespace() const noexcept override;
void GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const override;
};
};

0 comments on commit 3e851a9

Please sign in to comment.