28#include <wx/filename.h>
43std::vector<wxString> splitArgs(
const wxString& aText )
45 std::vector<wxString> args;
50 for( wxUniChar c : aText )
53 if( !quoted && !inArg && c ==
'#' )
61 else if( !quoted && ( c ==
' ' || c ==
'\t' ) )
64 args.push_back( current );
77 args.push_back( current );
84void splitKeyword(
const wxString& aLine, wxString& aKeyword, wxString& aArgs )
86 size_t end = aLine.find_first_of( wxS(
" \t=" ) );
88 if(
end == wxString::npos )
95 aKeyword = aLine.substr( 0,
end );
97 size_t start = aLine.find_first_not_of( wxS(
" \t" ),
end );
99 if( start != wxString::npos && aLine[start] ==
'=' )
100 start = aLine.find_first_not_of( wxS(
" \t" ), start + 1 );
102 aArgs = start == wxString::npos ? wxString() : aLine.substr( start );
108bool hostMatches(
const std::vector<wxString>& aPatterns,
const wxString& aHost )
110 bool matched =
false;
112 for(
const wxString& pattern : aPatterns )
114 const bool negated = pattern.StartsWith( wxS(
"!" ) );
115 wxString glob = negated ? pattern.Mid( 1 ) : pattern;
117 if( !aHost.Matches( glob ) )
130wxString expandPath(
const wxString& aValue,
const wxString& aHomeDir )
133 for(
size_t pos = aValue.find(
'%' ); pos != wxString::npos; pos = aValue.find(
'%', pos + 2 ) )
135 if( pos + 1 >= aValue.size() || aValue[pos + 1] !=
'd' )
140 wxString tail = aValue;
143 if( aValue == wxS(
"~" ) || aValue.StartsWith( wxS(
"~/" ) ) || aValue.StartsWith( wxS(
"~\\" ) ) )
146 tail = aValue.Mid( 1 );
149 tail.Replace( wxS(
"%d" ), aHomeDir );
157 const wxString& aHomeDir )
159 std::vector<wxString> files;
164 for( wxString line : wxSplit( aConfigText,
'\n',
'\0' ) )
166 line.Trim(
true ).Trim(
false );
168 if( line.IsEmpty() || line.StartsWith( wxS(
"#" ) ) )
173 splitKeyword( line, keyword, args );
176 if( keyword == wxS(
"host" ) )
178 active = hostMatches( splitArgs( args ), aHost );
180 else if( keyword == wxS(
"match" ) )
185 else if( active && keyword == wxS(
"identityfile" ) )
187 std::vector<wxString> values = splitArgs( args );
189 if( !values.empty() )
190 files.push_back( expandPath( values.front(), aHomeDir ) );
200 std::vector<wxString> keys;
203 if( aHomeDir.IsEmpty() )
206 wxFileName sshDir( aHomeDir, wxEmptyString );
207 sshDir.AppendDir( wxS(
".ssh" ) );
209 const wxString configPath = wxFileName( sshDir.GetPath(), wxS(
"config" ) ).GetFullPath();
211 std::ifstream
config( configPath.fn_str(), std::ios::binary );
215 std::string
text( ( std::istreambuf_iterator<char>(
config ) ), std::istreambuf_iterator<char>() );
218 if(
text.rfind(
"\xEF\xBB\xBF", 0 ) == 0 )
230 for(
const wxString&
name : { wxS(
"id_ed25519" ), wxS(
"id_ecdsa" ), wxS(
"id_rsa" ), wxS(
"id_dsa" ) } )
231 candidates.push_back( wxFileName( sshDir.GetPath(),
name ).GetFullPath() );
235 wxFileName fn( candidate );
241 const wxString
path = fn.GetFullPath();
243 bool seen = std::any_of( keys.begin(), keys.end(),
244 [&](
const wxString& aKey )
246 return IsSameSSHKey( aKey, path );
250 keys.push_back(
path );
259 if( aKey.IsEmpty() || aOther.IsEmpty() )
260 return aKey == aOther;
262 return wxFileName( aKey ).GetFullPath().IsSameAs( wxFileName( aOther ).GetFullPath(),
263 wxFileName::IsCaseSensitive() );
271 return wxGetHomeDir();
273 if(
const char* home = std::getenv(
"HOME" ); home && *home )
274 return wxString( home, wxConvLibc );
277 passwd* found =
nullptr;
278 std::vector<char> buffer( 1024 );
281 while( ( rc = getpwuid_r( getuid(), &entry, buffer.data(), buffer.size(), &found ) ) == ERANGE )
282 buffer.resize( buffer.size() * 2 );
284 if( rc == 0 && found && found->pw_dir )
285 return wxString( found->pw_dir, wxConvLibc );
287 return wxEmptyString;
294 return !aKey.IsEmpty() && wxFileName::FileExists( aKey ) && wxFileName::FileExists( aKey + wxS(
".pub" ) );
APIEXPORT bool IsOfferableSSHKey(const wxString &aKey)
Whether aKey can be offered to a server.
APIEXPORT std::vector< wxString > ParseSSHConfigIdentityFiles(const wxString &aConfigText, const wxString &aHost, const wxString &aHomeDir)
The IdentityFile entries an ssh config gives aHost, in file order.
APIEXPORT wxString HomeDir()
The user's home folder, where ssh looks for its config and default keys.
APIEXPORT bool IsSameSSHKey(const wxString &aKey, const wxString &aOther)
Whether two key paths name the same file.
APIEXPORT std::vector< wxString > DefaultSSHKeyCandidates(const wxString &aHomeDir, const wxString &aHost)
Private keys to offer aHost, best first.
static std::string decoded(const std::string &aBase64)
static std::vector< int > candidates(const SEGMENT_INDEX &aIndex, const SEG &aQuery, int aPadding)