Line data Source code
1 : /*! 2 : * \file esys/repo/git/sshenv_git.cpp 3 : * \brief Resolve explicit SSH identity paths from the process environment 4 : * 5 : * \cond 6 : * __legal_b__ 7 : * 8 : * Copyright (c) 2026 Michel Gillet 9 : * Distributed under the MIT License. 10 : * (See accompanying file LICENSE.txt or 11 : * copy at https://opensource.org/licenses/MIT) 12 : * 13 : * __legal_e__ 14 : * \endcond 15 : * 16 : */ 17 : 18 : #include "esys/repo/esysrepo_prec.h" 19 : 20 : #include <esys/repo/git/sshenv.h> 21 : 22 : #include <boost/filesystem.hpp> 23 : 24 : #include <cstdlib> 25 : #include <vector> 26 : 27 : namespace esys::repo::git 28 : { 29 : 30 : namespace 31 : { 32 : 33 56 : std::string getenv_str(const char *name) 34 : { 35 : #ifdef _WIN32 36 : char *buffer = nullptr; 37 : size_t length = 0; 38 : if (_dupenv_s(&buffer, &length, name) == 0 && buffer != nullptr) 39 : { 40 : std::string value(buffer); 41 : free(buffer); 42 : return value; 43 : } 44 : return {}; 45 : #else 46 56 : const char *value = std::getenv(name); 47 56 : return value != nullptr ? std::string(value) : std::string(); 48 : #endif 49 : } 50 : 51 6 : std::vector<std::string> tokenize_ssh_command(const std::string &command) 52 : { 53 6 : std::vector<std::string> tokens; 54 29 : for (std::size_t i = 0; i < command.size();) 55 : { 56 40 : while (i < command.size() && (command[i] == ' ' || command[i] == '\t')) ++i; 57 23 : if (i >= command.size()) break; 58 : 59 23 : if (command[i] == '"' || command[i] == '\'') 60 : { 61 2 : const char quote = command[i++]; 62 2 : const std::size_t start = i; 63 143 : while (i < command.size() && command[i] != quote) ++i; 64 4 : tokens.push_back(command.substr(start, i - start)); 65 2 : if (i < command.size()) ++i; 66 2 : continue; 67 2 : } 68 : 69 : const std::size_t start = i; 70 155 : while (i < command.size() && command[i] != ' ' && command[i] != '\t') ++i; 71 42 : tokens.push_back(command.substr(start, i - start)); 72 : } 73 6 : return tokens; 74 0 : } 75 : 76 : } // namespace 77 : 78 6 : std::string identity_file_from_git_ssh_command(const std::string &command) 79 : { 80 6 : const auto tokens = tokenize_ssh_command(command); 81 14 : for (std::size_t i = 0; i < tokens.size(); ++i) 82 : { 83 13 : const std::string &token = tokens[i]; 84 23 : if (token == "-i" || token == "--identity") 85 : { 86 4 : if (i + 1 < tokens.size()) return tokens[i + 1]; 87 0 : return {}; 88 : } 89 : // OpenSSH allows ``-ipath`` (no space). 90 9 : if (token.size() > 2 && token.compare(0, 2, "-i") == 0) return token.substr(2); 91 : } 92 1 : return {}; 93 6 : } 94 : 95 53 : std::string explicit_ssh_private_key_from_env() 96 : { 97 53 : const std::string ssh_private_key = getenv_str("SSH_PRIVATE_KEY"); 98 155 : if (!ssh_private_key.empty() && boost::filesystem::is_regular_file(ssh_private_key)) 99 50 : return ssh_private_key; 100 : 101 3 : const std::string git_ssh_command = getenv_str("GIT_SSH_COMMAND"); 102 3 : if (!git_ssh_command.empty()) 103 : { 104 1 : const std::string identity = identity_file_from_git_ssh_command(git_ssh_command); 105 3 : if (!identity.empty() && boost::filesystem::is_regular_file(identity)) return identity; 106 1 : } 107 : 108 3 : return {}; 109 53 : } 110 : 111 : } // namespace esys::repo::git