diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp index 0c6cf503092..e4565d4869e 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 04f0f38d7b8..dc667fe44a9 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 fb21fccdc31..3a4aab82329 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 00000000000..2f2da435793 --- /dev/null +++ b/src/cascadia/TerminalSettingsModel/SshHostGenerator.cpp @@ -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(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(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(SSH_EXE_PATH3))) + { + sshExePath = SSH_EXE_PATH3; + return true; + } + } + CATCH_LOG(); + + return false; +} + +static winrt::com_ptr 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& hostNames) noexcept +{ + try + { + const std::filesystem::path resolvedConfigPath{ wil::ExpandEnvironmentStringsW(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: +// - +// Return Value: +// - +void SshHostGenerator::GenerateProfiles(std::vector>& profiles) const +{ + std::wstring sshExePath; + if (tryFindSshExePath(sshExePath)) + { + std::vector hostNames; + + tryGetHostNamesFromConfigFile(SSH_SYSTEMCONFIG_PATH, hostNames); + tryGetHostNamesFromConfigFile(SSH_USERCONFIG_PATH, hostNames); + + for (const auto& hostName : hostNames) + { + profiles.emplace_back(makeProfile(sshExePath, hostName)); + } + } +} diff --git a/src/cascadia/TerminalSettingsModel/SshHostGenerator.h b/src/cascadia/TerminalSettingsModel/SshHostGenerator.h new file mode 100644 index 00000000000..f7ed9682158 --- /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; + }; +};