From cfc66e90de2e90ecae753506f5e813f6637cc5c1 Mon Sep 17 00:00:00 2001 From: "Jon Thysell (JAUNTY)" Date: Mon, 19 Sep 2022 17:13:42 -0700 Subject: [PATCH] Dynamically generate profile with .ssh/config This PR looks for the presense of OpenSSH config files and creates profiles from the hosts present. CLoses #9031 --- .../CascadiaSettingsSerialization.cpp | 2 + ...crosoft.Terminal.Settings.ModelLib.vcxproj | 2 + ...Terminal.Settings.ModelLib.vcxproj.filters | 6 ++ .../SshHostGenerator.cpp | 80 +++++++++++++++++++ .../TerminalSettingsModel/SshHostGenerator.h | 29 +++++++ 5 files changed, 119 insertions(+) create mode 100644 src/cascadia/TerminalSettingsModel/SshHostGenerator.cpp create mode 100644 src/cascadia/TerminalSettingsModel/SshHostGenerator.h diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp index 0c6cf5030926..e4565d4869eb 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp @@ -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. @@ -148,6 +149,7 @@ void SettingsLoader::GenerateProfiles() _executeGenerator(WslDistroGenerator{}); _executeGenerator(AzureCloudShellGenerator{}); _executeGenerator(VisualStudioGenerator{}); + _executeGenerator(SshHostGenerator{}); } // A new settings.json gets a special treatment: diff --git a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj index 04f0f38d7b8c..dc667fe44a9f 100644 --- a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj +++ b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj @@ -92,6 +92,7 @@ + @@ -165,6 +166,7 @@ + diff --git a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj.filters b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj.filters index fb21fccdc310..3a4aab823292 100644 --- a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj.filters +++ b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj.filters @@ -18,6 +18,9 @@ profileGeneration + + profileGeneration + @@ -61,6 +64,9 @@ profileGeneration + + profileGeneration + diff --git a/src/cascadia/TerminalSettingsModel/SshHostGenerator.cpp b/src/cascadia/TerminalSettingsModel/SshHostGenerator.cpp new file mode 100644 index 000000000000..189581885484 --- /dev/null +++ b/src/cascadia/TerminalSettingsModel/SshHostGenerator.cpp @@ -0,0 +1,80 @@ +// 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 SSH_TITLE_FMT{ L"SSH - {}" }; + +static constexpr std::wstring_view SSH_COMMANDLINE_FMT{ L"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 winrt::com_ptr _makeProfile(const std::wstring& hostName) +{ + const auto title = std::format(SSH_TITLE_FMT, hostName); + + const auto profile{ CreateDynamicProfile(title) }; + + std::wstring quotedCommandline = std::format(SSH_COMMANDLINE_FMT, hostName); + profile->Commandline(winrt::hstring{ quotedCommandline }); + + return profile; +} + +static void _tryGetHostNamesFromConfigFile(const std::wstring configPath, std::vector& hostNames) +{ + std::filesystem::path resolvedConfigPath{ wil::ExpandEnvironmentStringsW(configPath.c_str()) }; + if (std::filesystem::exists(resolvedConfigPath) && std::filesystem::is_regular_file(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())); + } + } + } +} + +// Method Description: +// - Generate a list of profiles for each detected OpenSSH host. +// Arguments: +// - +// Return Value: +// - +void SshHostGenerator::GenerateProfiles(std::vector>& profiles) const +{ + try + { + std::vector hostNames; + + _tryGetHostNamesFromConfigFile(SSH_SYSTEMCONFIG_PATH, hostNames); + _tryGetHostNamesFromConfigFile(SSH_USERCONFIG_PATH, hostNames); + + for (const auto& hostName : hostNames) + { + profiles.emplace_back(_makeProfile(hostName)); + } + } + CATCH_LOG(); +} diff --git a/src/cascadia/TerminalSettingsModel/SshHostGenerator.h b/src/cascadia/TerminalSettingsModel/SshHostGenerator.h new file mode 100644 index 000000000000..f7ed96821589 --- /dev/null +++ b/src/cascadia/TerminalSettingsModel/SshHostGenerator.h @@ -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>& profiles) const override; + }; +};