Skip to content

Commit

Permalink
PRE-MERGE #14042 Dynamically generate profiles from hosts in OpenSSH …
Browse files Browse the repository at this point in the history
…config files
  • Loading branch information
carlos-zamora committed Oct 25, 2022
2 parents 28ffe55 + 63feb70 commit d42b03f
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 1 deletion.
3 changes: 2 additions & 1 deletion doc/cascadia/profiles.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
"enum": [
"Windows.Terminal.Wsl",
"Windows.Terminal.Azure",
"Windows.Terminal.PowershellCore"
"Windows.Terminal.PowershellCore",
"Windows.Terminal.SSH"
],
"type": "string"
},
Expand Down
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 @@ -150,6 +151,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 @@ -110,6 +110,7 @@
<ClInclude Include="VsDevShellGenerator.h" />
<ClInclude Include="VsSetupConfiguration.h" />
<ClInclude Include="WslDistroGenerator.h" />
<ClInclude Include="SshHostGenerator.h" />
<ClInclude Include="ModelSerializationHelpers.h" />
</ItemGroup>
<!-- ========================= Cpp Files ======================== -->
Expand Down Expand Up @@ -202,6 +203,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
161 changes: 161 additions & 0 deletions src/cascadia/TerminalSettingsModel/SshHostGenerator.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// 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 std::wstring_view PROFILE_TITLE_PREFIX = L"SSH - ";
static constexpr std::wstring_view PROFILE_ICON_PATH = L"ms-appx:///ProfileIcons/{550ce7b8-d500-50ad-8a1a-c400c3262db3}.png";

// OpenSSH is installed under System32 when installed via Optional Features
static constexpr std::wstring_view SSH_EXE_PATH1 = L"%SystemRoot%\\System32\\OpenSSH\\ssh.exe";

// OpenSSH (x86/x64) is installed under Program Files when installed via MSI
static constexpr std::wstring_view SSH_EXE_PATH2 = L"%ProgramFiles%\\OpenSSH\\ssh.exe";

// OpenSSH (x86) is installed under Program Files x86 when installed via MSI on x64 machine
static constexpr std::wstring_view SSH_EXE_PATH3 = L"%ProgramFiles(x86)%\\OpenSSH\\ssh.exe";

static constexpr std::wstring_view SSH_SYSTEMCONFIG_PATH = L"%ProgramData%\\ssh\\ssh_config";
static constexpr std::wstring_view SSH_USERCONFIG_PATH = L"%UserProfile%\\.ssh\\config";

static constexpr std::wstring_view SSH_CONFIG_HOST_KEY{ L"Host" };
static constexpr std::wstring_view SSH_CONFIG_HOSTNAME_KEY{ L"HostName" };

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

/*static*/ const std::wregex SshHostGenerator::_configKeyValueRegex{ LR"(^\s*(\w+)\s+([^\s]+.*[^\s])\s*$)" };

/*static*/ std::wstring_view SshHostGenerator::_getProfileName(const std::wstring_view& hostName) noexcept
{
return std::wstring_view{ L"" + PROFILE_TITLE_PREFIX + hostName };
}

/*static*/ std::wstring_view SshHostGenerator::_getProfileIconPath() noexcept
{
return PROFILE_ICON_PATH;
}

/*static*/ std::wstring_view SshHostGenerator::_getProfileCommandLine(const std::wstring_view& sshExePath, const std::wstring_view& hostName) noexcept
{
return std::wstring_view{ L"\"" + sshExePath + L"\" " + hostName };
}

/*static*/ bool SshHostGenerator::_tryFindSshExePath(std::wstring& sshExePath) noexcept
{
try
{
for (const auto& path : { SSH_EXE_PATH1, SSH_EXE_PATH2, SSH_EXE_PATH3 })
{
if (std::filesystem::exists(wil::ExpandEnvironmentStringsW<std::wstring>(path.data())))
{
sshExePath = path;
return true;
}
}
}
CATCH_LOG();

return false;
}

/*static*/ bool SshHostGenerator::_tryParseConfigKeyValue(const std::wstring_view& line, std::wstring& key, std::wstring& value) noexcept
{
try
{
if (!line.empty() && !line.starts_with(L"#"))
{
std::wstring input{ line };
std::wsmatch match;
if (std::regex_search(input, match, SshHostGenerator::_configKeyValueRegex))
{
key = match[1];
value = match[2];
return true;
}
}
}
CATCH_LOG();

return false;
}

/*static*/ void SshHostGenerator::_getHostNamesFromConfigFile(const std::wstring_view& configPath, std::vector<std::wstring>& hostNames) noexcept
{
try
{
const std::filesystem::path resolvedConfigPath{ wil::ExpandEnvironmentStringsW<std::wstring>(configPath.data()) };
if (std::filesystem::exists(resolvedConfigPath))
{
std::wifstream inputStream(resolvedConfigPath);

std::wstring line;
std::wstring key;
std::wstring value;

std::wstring lastHost;

while (std::getline(inputStream, line))
{
if (_tryParseConfigKeyValue(line, key, value))
{
if (til::equals_insensitive_ascii(key, SSH_CONFIG_HOST_KEY))
{
// Save potential Host value for later
lastHost = value;
}
else if (til::equals_insensitive_ascii(key, SSH_CONFIG_HOSTNAME_KEY))
{
// HostName was specified
if (!lastHost.empty())
{
hostNames.emplace_back(lastHost);
lastHost = L"";
}
}
}
}
}
}
CATCH_LOG();
}

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

// 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;

_getHostNamesFromConfigFile(SSH_SYSTEMCONFIG_PATH, hostNames);
_getHostNamesFromConfigFile(SSH_USERCONFIG_PATH, hostNames);

for (const auto& hostName : hostNames)
{
const auto profile{ CreateDynamicProfile(_getProfileName(hostName)) };

profile->Commandline(winrt::hstring{ _getProfileCommandLine(sshExePath, hostName) });
profile->Icon(winrt::hstring{ _getProfileIconPath() });

profiles.emplace_back(profile);
}
}
}
40 changes: 40 additions & 0 deletions src/cascadia/TerminalSettingsModel/SshHostGenerator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*++
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;

private:
static const std::wregex _configKeyValueRegex;

static std::wstring_view _getProfileName(const std::wstring_view& hostName) noexcept;
static std::wstring_view _getProfileIconPath() noexcept;
static std::wstring_view _getProfileCommandLine(const std::wstring_view& sshExePath, const std::wstring_view& hostName) noexcept;

static bool _tryFindSshExePath(std::wstring& sshExePath) noexcept;
static bool _tryParseConfigKeyValue(const std::wstring_view& line, std::wstring& key, std::wstring& value) noexcept;
static void _getHostNamesFromConfigFile(const std::wstring_view& configPath, std::vector<std::wstring>& hostNames) noexcept;
};
};

0 comments on commit d42b03f

Please sign in to comment.