Unverified Commit 448de4d6 by Edward Thomson Committed by GitHub

Merge pull request #6474 from libgit2/ethomson/v1.5.2

v1.5: support SSH known hosts on Windows
parents 42e5db98 41e3f6dd
......@@ -176,19 +176,25 @@ jobs:
- name: "Windows (amd64, Visual Studio)"
id: windows-amd64-vs
os: windows-2019
setup-script: win32
env:
ARCH: amd64
CMAKE_GENERATOR: Visual Studio 16 2019
CMAKE_OPTIONS: -A x64 -DWIN32_LEAKCHECK=ON -DDEPRECATE_HARD=ON
CMAKE_OPTIONS: -A x64 -DWIN32_LEAKCHECK=ON -DDEPRECATE_HARD=ON -DUSE_SSH=ON -DCMAKE_PREFIX_PATH=D:\Temp\libssh2
BUILD_PATH: C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Program Files (x86)\CMake\bin;D:\Temp\libssh2\bin
BUILD_TEMP: D:\Temp
SKIP_SSH_TESTS: true
SKIP_NEGOTIATE_TESTS: true
- name: "Windows (x86, Visual Studio)"
id: windows-x86-vs
os: windows-2019
setup-script: win32
env:
ARCH: x86
CMAKE_GENERATOR: Visual Studio 16 2019
CMAKE_OPTIONS: -A Win32 -DWIN32_LEAKCHECK=ON -DDEPRECATE_HARD=ON -DUSE_SHA1=HTTPS -DUSE_BUNDLED_ZLIB=ON
CMAKE_OPTIONS: -A Win32 -DWIN32_LEAKCHECK=ON -DDEPRECATE_HARD=ON -DUSE_SHA1=HTTPS -DUSE_BUNDLED_ZLIB=ON -DUSE_SSH=ON -DCMAKE_PREFIX_PATH=D:\Temp\libssh2
BUILD_PATH: C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Program Files (x86)\CMake\bin;D:\Temp\libssh2\bin
BUILD_TEMP: D:\Temp
SKIP_SSH_TESTS: true
SKIP_NEGOTIATE_TESTS: true
- name: "Windows (amd64, mingw)"
......@@ -251,6 +257,10 @@ jobs:
- name: Build and test
run: |
export GITTEST_NEGOTIATE_PASSWORD="${{ secrets.GITTEST_NEGOTIATE_PASSWORD }}"
export GITTEST_GITHUB_SSH_KEY="${{ secrets.GITTEST_GITHUB_SSH_KEY }}"
export GITTEST_GITHUB_SSH_PUBKEY="${{ secrets.GITTEST_GITHUB_SSH_PUBKEY }}"
export GITTEST_GITHUB_SSH_PASSPHRASE="${{ secrets.GITTEST_GITHUB_SSH_PASSPHRASE }}"
export GITTEST_GITHUB_SSH_REMOTE_HOSTKEY="${{ secrets.GITTEST_GITHUB_SSH_REMOTE_HOSTKEY }}"
if [ -n "${{ matrix.platform.container.name }}" ]; then
mkdir build
......
......@@ -13,16 +13,30 @@ BUILD_PATH=${BUILD_PATH:=$PATH}
CMAKE=$(which cmake)
CMAKE_GENERATOR=${CMAKE_GENERATOR:-Unix Makefiles}
indent() { sed "s/^/ /"; }
cygfullpath() {
result=$(echo "${1}" | tr \; \\n | while read -r element; do
if [ "${last}" != "" ]; then echo -n ":"; fi
echo -n $(cygpath "${element}")
last="${element}"
done)
if [ "${result}" = "" ]; then exit 1; fi
echo "${result}"
}
if [[ "$(uname -s)" == MINGW* ]]; then
BUILD_PATH=$(cygpath "$BUILD_PATH")
BUILD_PATH=$(cygfullpath "${BUILD_PATH}")
fi
indent() { sed "s/^/ /"; }
echo "Source directory: ${SOURCE_DIR}"
echo "Build directory: ${BUILD_DIR}"
echo ""
echo "Platform:"
uname -s | indent
if [ "$(uname -s)" = "Darwin" ]; then
echo "macOS version:"
sw_vers | indent
......@@ -40,7 +54,7 @@ echo "Kernel version:"
uname -a 2>&1 | indent
echo "CMake version:"
env PATH="${BUILD_PATH}" "${CMAKE}" --version 2>&1 | indent
env PATH="${BUILD_PATH}" "${CMAKE}" --version | head -1 2>&1 | indent
if test -n "${CC}"; then
echo "Compiler version:"
......
#!/bin/sh
set -ex
echo "##############################################################################"
echo "## Downloading libssh2"
echo "##############################################################################"
BUILD_TEMP=${BUILD_TEMP:=$TEMP}
BUILD_TEMP=$(cygpath $BUILD_TEMP)
case "$ARCH" in
amd64)
LIBSSH2_URI="https://github.com/libgit2/ci-dependencies/releases/download/2023-02-01/libssh2-20230201-amd64.zip";;
x86)
LIBSSH2_URI="https://github.com/libgit2/ci-dependencies/releases/download/2023-02-01-v2/libssh2-20230201-x86.zip";;
esac
if [ -z "$LIBSSH2_URI" ]; then
echo "No URL"
exit 1
fi
mkdir -p "$BUILD_TEMP"
curl -s -L "$LIBSSH2_URI" -o "$BUILD_TEMP"/libssh2-"$ARCH".zip
unzip -q "$BUILD_TEMP"/libssh2-"$ARCH".zip -d "$BUILD_TEMP"
......@@ -13,6 +13,8 @@ fi
SOURCE_DIR=${SOURCE_DIR:-$( cd "$( dirname "${BASH_SOURCE[0]}" )" && dirname $( pwd ) )}
BUILD_DIR=$(pwd)
BUILD_PATH=${BUILD_PATH:=$PATH}
CTEST=$(which ctest)
TMPDIR=${TMPDIR:-/tmp}
USER=${USER:-$(whoami)}
......@@ -52,7 +54,11 @@ run_test() {
RETURN_CODE=0
CLAR_SUMMARY="${BUILD_DIR}/results_${1}.xml" ctest -V -R "^${1}$" || RETURN_CODE=$? && true
(
export PATH="${BUILD_PATH}"
export CLAR_SUMMARY="${BUILD_DIR}/results_${1}.xml"
"${CTEST}" -V -R "^${1}$"
) || RETURN_CODE=$? && true
if [ "$RETURN_CODE" -eq 0 ]; then
FAILED=0
......@@ -73,9 +79,31 @@ run_test() {
fi
}
indent() { sed "s/^/ /"; }
cygfullpath() {
result=$(echo "${1}" | tr \; \\n | while read -r element; do
if [ "${last}" != "" ]; then echo -n ":"; fi
echo -n $(cygpath "${element}")
last="${element}"
done)
if [ "${result}" = "" ]; then exit 1; fi
echo "${result}"
}
if [[ "$(uname -s)" == MINGW* ]]; then
BUILD_PATH=$(cygfullpath "$BUILD_PATH")
fi
# Configure the test environment; run them early so that we're certain
# that they're started by the time we need them.
echo "CTest version:"
env PATH="${BUILD_PATH}" "${CTEST}" --version | head -1 2>&1 | indent
echo ""
echo "##############################################################################"
echo "## Configuring test environment"
echo "##############################################################################"
......@@ -348,7 +376,7 @@ if [ -z "$SKIP_FUZZERS" ]; then
echo "## Running fuzzers"
echo "##############################################################################"
ctest -V -R 'fuzzer'
env PATH="${BUILD_PATH}" "${CTEST}" -V -R 'fuzzer'
fi
cleanup
......
......@@ -12,16 +12,262 @@
#include "fs_path.h"
#include <ctype.h>
#if GIT_WIN32
#include "win32/findfile.h"
# include "fs_path.h"
# include "win32/path_w32.h"
# include "win32/utf-conv.h"
#else
#include <unistd.h>
#include <pwd.h>
# include <unistd.h>
# include <pwd.h>
#endif
#ifdef GIT_WIN32
# define REG_GITFORWINDOWS_KEY L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1"
# define REG_GITFORWINDOWS_KEY_WOW64 L"SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1"
static int expand_win32_path(git_win32_path dest, const wchar_t *src)
{
DWORD len = ExpandEnvironmentStringsW(src, dest, GIT_WIN_PATH_UTF16);
if (!len || len > GIT_WIN_PATH_UTF16)
return -1;
return 0;
}
static int win32_path_to_utf8(git_str *dest, const wchar_t *src)
{
git_win32_utf8_path utf8_path;
if (git_win32_path_to_utf8(utf8_path, src) < 0) {
git_error_set(GIT_ERROR_OS, "unable to convert path to UTF-8");
return -1;
}
/* Convert backslashes to forward slashes */
git_fs_path_mkposix(utf8_path);
return git_str_sets(dest, utf8_path);
}
static git_win32_path mock_registry;
static bool mock_registry_set;
extern int git_win32__set_registry_system_dir(const wchar_t *mock_sysdir)
{
if (!mock_sysdir) {
mock_registry[0] = L'\0';
mock_registry_set = false;
} else {
size_t len = wcslen(mock_sysdir);
if (len > GIT_WIN_PATH_MAX) {
git_error_set(GIT_ERROR_INVALID, "mock path too long");
return -1;
}
wcscpy(mock_registry, mock_sysdir);
mock_registry_set = true;
}
return 0;
}
static int lookup_registry_key(
git_win32_path out,
const HKEY hive,
const wchar_t* key,
const wchar_t *value)
{
HKEY hkey;
DWORD type, size;
int error = GIT_ENOTFOUND;
/*
* Registry data may not be NUL terminated, provide room to do
* it ourselves.
*/
size = (DWORD)((sizeof(git_win32_path) - 1) * sizeof(wchar_t));
if (RegOpenKeyExW(hive, key, 0, KEY_READ, &hkey) != 0)
return GIT_ENOTFOUND;
if (RegQueryValueExW(hkey, value, NULL, &type, (LPBYTE)out, &size) == 0 &&
type == REG_SZ &&
size > 0 &&
size < sizeof(git_win32_path)) {
size_t wsize = size / sizeof(wchar_t);
size_t len = wsize - 1;
if (out[wsize - 1] != L'\0') {
len = wsize;
out[wsize] = L'\0';
}
if (out[len - 1] == L'\\')
out[len - 1] = L'\0';
if (_waccess(out, F_OK) == 0)
error = 0;
}
RegCloseKey(hkey);
return error;
}
static int find_sysdir_in_registry(git_win32_path out)
{
if (mock_registry_set) {
if (mock_registry[0] == L'\0')
return GIT_ENOTFOUND;
wcscpy(out, mock_registry);
return 0;
}
if (lookup_registry_key(out, HKEY_CURRENT_USER, REG_GITFORWINDOWS_KEY, L"InstallLocation") == 0 ||
lookup_registry_key(out, HKEY_CURRENT_USER, REG_GITFORWINDOWS_KEY_WOW64, L"InstallLocation") == 0 ||
lookup_registry_key(out, HKEY_LOCAL_MACHINE, REG_GITFORWINDOWS_KEY, L"InstallLocation") == 0 ||
lookup_registry_key(out, HKEY_LOCAL_MACHINE, REG_GITFORWINDOWS_KEY_WOW64, L"InstallLocation") == 0)
return 0;
return GIT_ENOTFOUND;
}
static int find_sysdir_in_path(git_win32_path out)
{
size_t out_len;
if (git_win32_path_find_executable(out, L"git.exe") < 0 &&
git_win32_path_find_executable(out, L"git.cmd") < 0)
return GIT_ENOTFOUND;
out_len = wcslen(out);
/* Trim the file name */
if (out_len <= CONST_STRLEN(L"git.exe"))
return GIT_ENOTFOUND;
out_len -= CONST_STRLEN(L"git.exe");
if (out_len && out[out_len - 1] == L'\\')
out_len--;
/*
* Git for Windows usually places the command in a 'bin' or
* 'cmd' directory, trim that.
*/
if (out_len >= CONST_STRLEN(L"\\bin") &&
wcsncmp(&out[out_len - CONST_STRLEN(L"\\bin")], L"\\bin", CONST_STRLEN(L"\\bin")) == 0)
out_len -= CONST_STRLEN(L"\\bin");
else if (out_len >= CONST_STRLEN(L"\\cmd") &&
wcsncmp(&out[out_len - CONST_STRLEN(L"\\cmd")], L"\\cmd", CONST_STRLEN(L"\\cmd")) == 0)
out_len -= CONST_STRLEN(L"\\cmd");
if (!out_len)
return GIT_ENOTFOUND;
out[out_len] = L'\0';
return 0;
}
static int find_win32_dirs(
git_str *out,
const wchar_t* tmpl[])
{
git_win32_path path16;
git_str buf = GIT_STR_INIT;
git_str_clear(out);
for (; *tmpl != NULL; tmpl++) {
if (!expand_win32_path(path16, *tmpl) &&
path16[0] != L'%' &&
!_waccess(path16, F_OK)) {
win32_path_to_utf8(&buf, path16);
if (buf.size)
git_str_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr);
}
}
git_str_dispose(&buf);
return (git_str_oom(out) ? -1 : 0);
}
static int append_subdir(git_str *out, git_str *path, const char *subdir)
{
static const char* architecture_roots[] = {
"",
"mingw64",
"mingw32",
NULL
};
const char **root;
size_t orig_path_len = path->size;
for (root = architecture_roots; *root; root++) {
if ((*root[0] && git_str_joinpath(path, path->ptr, *root) < 0) ||
git_str_joinpath(path, path->ptr, subdir) < 0)
return -1;
if (git_fs_path_exists(path->ptr) &&
git_str_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, path->ptr) < 0)
return -1;
git_str_truncate(path, orig_path_len);
}
return 0;
}
int git_win32__find_system_dirs(git_str *out, const char *subdir)
{
git_win32_path pathdir, regdir;
git_str path8 = GIT_STR_INIT;
bool has_pathdir, has_regdir;
int error;
has_pathdir = (find_sysdir_in_path(pathdir) == 0);
has_regdir = (find_sysdir_in_registry(regdir) == 0);
if (!has_pathdir && !has_regdir)
return 0;
/*
* Usually the git in the path is the same git in the registry,
* in this case there's no need to duplicate the paths.
*/
if (has_pathdir && has_regdir && wcscmp(pathdir, regdir) == 0)
has_regdir = false;
if (has_pathdir) {
if ((error = win32_path_to_utf8(&path8, pathdir)) < 0 ||
(error = append_subdir(out, &path8, subdir)) < 0)
goto done;
}
if (has_regdir) {
if ((error = win32_path_to_utf8(&path8, regdir)) < 0 ||
(error = append_subdir(out, &path8, subdir)) < 0)
goto done;
}
done:
git_str_dispose(&path8);
return error;
}
#endif /* WIN32 */
static int git_sysdir_guess_programdata_dirs(git_str *out)
{
#ifdef GIT_WIN32
return git_win32__find_programdata_dirs(out);
static const wchar_t *programdata_tmpls[2] = {
L"%PROGRAMDATA%\\Git",
NULL,
};
return find_win32_dirs(out, programdata_tmpls);
#else
git_str_clear(out);
return 0;
......@@ -75,10 +321,17 @@ out:
}
#endif
static int git_sysdir_guess_global_dirs(git_str *out)
static int git_sysdir_guess_home_dirs(git_str *out)
{
#ifdef GIT_WIN32
return git_win32__find_global_dirs(out);
static const wchar_t *global_tmpls[4] = {
L"%HOME%\\",
L"%HOMEDRIVE%%HOMEPATH%\\",
L"%USERPROFILE%\\",
NULL,
};
return find_win32_dirs(out, global_tmpls);
#else
int error;
uid_t uid, euid;
......@@ -114,10 +367,25 @@ static int git_sysdir_guess_global_dirs(git_str *out)
#endif
}
static int git_sysdir_guess_global_dirs(git_str *out)
{
return git_sysdir_guess_home_dirs(out);
}
static int git_sysdir_guess_xdg_dirs(git_str *out)
{
#ifdef GIT_WIN32
return git_win32__find_xdg_dirs(out);
static const wchar_t *global_tmpls[7] = {
L"%XDG_CONFIG_HOME%\\git",
L"%APPDATA%\\git",
L"%LOCALAPPDATA%\\git",
L"%HOME%\\.config\\git",
L"%HOMEDRIVE%%HOMEPATH%\\.config\\git",
L"%USERPROFILE%\\.config\\git",
NULL,
};
return find_win32_dirs(out, global_tmpls);
#else
git_str env = GIT_STR_INIT;
int error;
......@@ -171,6 +439,7 @@ static struct git_sysdir__dir git_sysdir__dirs[] = {
{ GIT_STR_INIT, git_sysdir_guess_xdg_dirs },
{ GIT_STR_INIT, git_sysdir_guess_programdata_dirs },
{ GIT_STR_INIT, git_sysdir_guess_template_dirs },
{ GIT_STR_INIT, git_sysdir_guess_home_dirs }
};
static void git_sysdir_global_shutdown(void)
......@@ -350,6 +619,12 @@ int git_sysdir_find_template_dir(git_str *path)
path, NULL, GIT_SYSDIR_TEMPLATE, "template");
}
int git_sysdir_find_homedir(git_str *path)
{
return git_sysdir_find_in_dirlist(
path, NULL, GIT_SYSDIR_HOME, "home directory");
}
int git_sysdir_expand_global_file(git_str *path, const char *filename)
{
int error;
......@@ -361,3 +636,15 @@ int git_sysdir_expand_global_file(git_str *path, const char *filename)
return error;
}
int git_sysdir_expand_homedir_file(git_str *path, const char *filename)
{
int error;
if ((error = git_sysdir_find_homedir(path)) == 0) {
if (filename)
error = git_str_joinpath(path, path->ptr, filename);
}
return error;
}
......@@ -57,10 +57,22 @@ extern int git_sysdir_find_programdata_file(git_str *path, const char *filename)
extern int git_sysdir_find_template_dir(git_str *path);
/**
* Expand the name of a "global" file (i.e. one in a user's home
* directory). Unlike `find_global_file` (above), this makes no
* attempt to check for the existence of the file, and is useful if
* you want the full path regardless of existence.
* Find the home directory. On Windows, this will look at the `HOME`,
* `HOMEPATH`, and `USERPROFILE` environment variables (in that order)
* and return the first path that is set and exists. On other systems,
* this will simply return the contents of the `HOME` environment variable.
*
* @param path buffer to write the full path into
* @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error
*/
extern int git_sysdir_find_homedir(git_str *path);
/**
* Expand the name of a "global" file -- by default inside the user's
* home directory, but can be overridden by the user configuration.
* Unlike `find_global_file` (above), this makes no attempt to check
* for the existence of the file, and is useful if you want the full
* path regardless of existence.
*
* @param path buffer to write the full path into
* @param filename name of file in the home directory
......@@ -68,13 +80,25 @@ extern int git_sysdir_find_template_dir(git_str *path);
*/
extern int git_sysdir_expand_global_file(git_str *path, const char *filename);
/**
* Expand the name of a file in the user's home directory. This
* function makes no attempt to check for the existence of the file,
* and is useful if you want the full path regardless of existence.
*
* @param path buffer to write the full path into
* @param filename name of file in the home directory
* @return 0 on success or -1 on error
*/
extern int git_sysdir_expand_homedir_file(git_str *path, const char *filename);
typedef enum {
GIT_SYSDIR_SYSTEM = 0,
GIT_SYSDIR_GLOBAL = 1,
GIT_SYSDIR_XDG = 2,
GIT_SYSDIR_SYSTEM = 0,
GIT_SYSDIR_GLOBAL = 1,
GIT_SYSDIR_XDG = 2,
GIT_SYSDIR_PROGRAMDATA = 3,
GIT_SYSDIR_TEMPLATE = 4,
GIT_SYSDIR__MAX = 5
GIT_SYSDIR_TEMPLATE = 4,
GIT_SYSDIR_HOME = 5,
GIT_SYSDIR__MAX = 6
} git_sysdir_t;
/**
......@@ -110,4 +134,10 @@ extern int git_sysdir_set(git_sysdir_t which, const char *paths);
*/
extern int git_sysdir_reset(void);
/** Sets the registry system dir to a mock; for testing. */
extern int git_win32__set_registry_system_dir(const wchar_t *mock_sysdir);
/** Find the given system dir; for testing. */
extern int git_win32__find_system_dirs(git_str *out, const char *subdir);
#endif
......@@ -16,6 +16,7 @@
#include "netops.h"
#include "smart.h"
#include "streams/socket.h"
#include "sysdir.h"
#include "git2/credential.h"
#include "git2/sys/credential.h"
......@@ -421,7 +422,8 @@ static int request_creds(git_credential **out, ssh_subtransport *t, const char *
return 0;
}
#define KNOWN_HOSTS_FILE ".ssh/known_hosts"
#define SSH_DIR ".ssh"
#define KNOWN_HOSTS_FILE "known_hosts"
/*
* Load the known_hosts file.
......@@ -430,16 +432,14 @@ static int request_creds(git_credential **out, ssh_subtransport *t, const char *
*/
static int load_known_hosts(LIBSSH2_KNOWNHOSTS **hosts, LIBSSH2_SESSION *session)
{
git_str path = GIT_STR_INIT, home = GIT_STR_INIT;
git_str path = GIT_STR_INIT, sshdir = GIT_STR_INIT;
LIBSSH2_KNOWNHOSTS *known_hosts = NULL;
int error;
GIT_ASSERT_ARG(hosts);
if ((error = git__getenv(&home, "HOME")) < 0)
return error;
if ((error = git_str_joinpath(&path, git_str_cstr(&home), KNOWN_HOSTS_FILE)) < 0)
if ((error = git_sysdir_expand_homedir_file(&sshdir, SSH_DIR)) < 0 ||
(error = git_str_joinpath(&path, git_str_cstr(&sshdir), KNOWN_HOSTS_FILE)) < 0)
goto out;
if ((known_hosts = libssh2_knownhost_init(session)) == NULL) {
......@@ -461,34 +461,32 @@ static int load_known_hosts(LIBSSH2_KNOWNHOSTS **hosts, LIBSSH2_SESSION *session
out:
*hosts = known_hosts;
git_str_clear(&home);
git_str_clear(&path);
git_str_dispose(&sshdir);
git_str_dispose(&path);
return error;
}
static const char *hostkey_type_to_string(int type)
static void add_hostkey_pref_if_avail(
LIBSSH2_KNOWNHOSTS *known_hosts,
const char *hostname,
int port,
git_str *prefs,
int type,
const char *type_name)
{
switch (type) {
case LIBSSH2_KNOWNHOST_KEY_SSHRSA:
return "ssh-rsa";
case LIBSSH2_KNOWNHOST_KEY_SSHDSS:
return "ssh-dss";
#ifdef LIBSSH2_KNOWNHOST_KEY_ECDSA_256
case LIBSSH2_KNOWNHOST_KEY_ECDSA_256:
return "ecdsa-sha2-nistp256";
case LIBSSH2_KNOWNHOST_KEY_ECDSA_384:
return "ecdsa-sha2-nistp384";
case LIBSSH2_KNOWNHOST_KEY_ECDSA_521:
return "ecdsa-sha2-nistp521";
#endif
#ifdef LIBSSH2_KNOWNHOST_KEY_ED25519
case LIBSSH2_KNOWNHOST_KEY_ED25519:
return "ssh-ed25519";
#endif
}
struct libssh2_knownhost *host = NULL;
const char key = '\0';
int mask = LIBSSH2_KNOWNHOST_TYPE_PLAIN | LIBSSH2_KNOWNHOST_KEYENC_RAW | type;
int error;
return NULL;
error = libssh2_knownhost_checkp(known_hosts, hostname, port, &key, 1, mask, &host);
if (error == LIBSSH2_KNOWNHOST_CHECK_MISMATCH) {
if (git_str_len(prefs) > 0) {
git_str_putc(prefs, ',');
}
git_str_puts(prefs, type_name);
}
}
/*
......@@ -496,27 +494,27 @@ static const char *hostkey_type_to_string(int type)
* look it up with a nonsense key and using that mismatch to figure out what key
* we do have stored for the host.
*
* Returns the string to pass to libssh2_session_method_pref or NULL if we were
* unable to find anything or an error happened.
* Populates prefs with the string to pass to libssh2_session_method_pref.
*/
static const char *find_hostkey_preference(LIBSSH2_KNOWNHOSTS *known_hosts, const char *hostname, int port)
static void find_hostkey_preference(
LIBSSH2_KNOWNHOSTS *known_hosts,
const char *hostname,
int port,
git_str *prefs)
{
struct libssh2_knownhost *host = NULL;
/* Specify no key type so we don't filter on that */
int type = LIBSSH2_KNOWNHOST_TYPE_PLAIN | LIBSSH2_KNOWNHOST_KEYENC_RAW;
const char key = '\0';
int error;
/*
* In case of mismatch, we can find the type of key from known_hosts in
* the returned host's information as it means that an entry was found
* but our nonsense key obviously didn't match.
* The order here is important as it indicates the priority of what will
* be preferred.
*/
error = libssh2_knownhost_checkp(known_hosts, hostname, port, &key, 1, type, &host);
if (error == LIBSSH2_KNOWNHOST_CHECK_MISMATCH)
return hostkey_type_to_string(host->typemask & LIBSSH2_KNOWNHOST_KEY_MASK);
return NULL;
#ifdef LIBSSH2_KNOWNHOST_KEY_ED25519
add_hostkey_pref_if_avail(known_hosts, hostname, port, prefs, LIBSSH2_KNOWNHOST_KEY_ED25519, "ssh-ed25519");
#endif
#ifdef LIBSSH2_KNOWNHOST_KEY_ECDSA_256
add_hostkey_pref_if_avail(known_hosts, hostname, port, prefs, LIBSSH2_KNOWNHOST_KEY_ECDSA_256, "ecdsa-sha2-nistp256");
add_hostkey_pref_if_avail(known_hosts, hostname, port, prefs, LIBSSH2_KNOWNHOST_KEY_ECDSA_384, "ecdsa-sha2-nistp384");
add_hostkey_pref_if_avail(known_hosts, hostname, port, prefs, LIBSSH2_KNOWNHOST_KEY_ECDSA_521, "ecdsa-sha2-nistp521");
#endif
add_hostkey_pref_if_avail(known_hosts, hostname, port, prefs, LIBSSH2_KNOWNHOST_KEY_SSHRSA, "ssh-rsa");
}
static int _git_ssh_session_create(
......@@ -526,11 +524,11 @@ static int _git_ssh_session_create(
int port,
git_stream *io)
{
int rc = 0;
git_socket_stream *socket = GIT_CONTAINER_OF(io, git_socket_stream, parent);
LIBSSH2_SESSION *s;
LIBSSH2_KNOWNHOSTS *known_hosts;
git_socket_stream *socket = GIT_CONTAINER_OF(io, git_socket_stream, parent);
const char *keytype = NULL;
git_str prefs = GIT_STR_INIT;
int rc = 0;
GIT_ASSERT_ARG(session);
GIT_ASSERT_ARG(hosts);
......@@ -547,16 +545,17 @@ static int _git_ssh_session_create(
return -1;
}
if ((keytype = find_hostkey_preference(known_hosts, hostname, port)) != NULL) {
find_hostkey_preference(known_hosts, hostname, port, &prefs);
if (git_str_len(&prefs) > 0) {
do {
rc = libssh2_session_method_pref(s, LIBSSH2_METHOD_HOSTKEY, keytype);
rc = libssh2_session_method_pref(s, LIBSSH2_METHOD_HOSTKEY, git_str_cstr(&prefs));
} while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc);
if (rc != LIBSSH2_ERROR_NONE) {
ssh_error(s, "failed to set hostkey preference");
goto on_error;
}
}
git_str_dispose(&prefs);
do {
rc = libssh2_session_handshake(s, socket->s);
......@@ -753,7 +752,7 @@ static int check_certificate(
if (error == GIT_PASSTHROUGH) {
error = git_error_state_restore(&previous_error);
} else if (error < 0 && !git_error_last()) {
git_error_set(GIT_ERROR_NET, "user canceled hostkey check");
git_error_set(GIT_ERROR_NET, "unknown remote host key");
}
git_error_state_free(&previous_error);
......@@ -1009,7 +1008,7 @@ static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *use
/* either error, or the remote accepts NONE auth, which is bizarre, let's punt */
if (list == NULL && !libssh2_userauth_authenticated(session)) {
ssh_error(session, "Failed to retrieve list of SSH authentication methods");
ssh_error(session, "remote rejected authentication");
return GIT_EAUTH;
}
......
......@@ -13,9 +13,6 @@
#include "rand.h"
#include <ctype.h>
#if GIT_WIN32
#include "win32/findfile.h"
#endif
#define GIT_FILEMODE_DEFAULT 0100666
......
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "findfile.h"
#include "path_w32.h"
#include "utf-conv.h"
#include "fs_path.h"
#define REG_GITFORWINDOWS_KEY L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1"
#define REG_GITFORWINDOWS_KEY_WOW64 L"SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1"
static int git_win32__expand_path(git_win32_path dest, const wchar_t *src)
{
DWORD len = ExpandEnvironmentStringsW(src, dest, GIT_WIN_PATH_UTF16);
if (!len || len > GIT_WIN_PATH_UTF16)
return -1;
return 0;
}
static int win32_path_to_8(git_str *dest, const wchar_t *src)
{
git_win32_utf8_path utf8_path;
if (git_win32_path_to_utf8(utf8_path, src) < 0) {
git_error_set(GIT_ERROR_OS, "unable to convert path to UTF-8");
return -1;
}
/* Convert backslashes to forward slashes */
git_fs_path_mkposix(utf8_path);
return git_str_sets(dest, utf8_path);
}
static git_win32_path mock_registry;
static bool mock_registry_set;
extern int git_win32__set_registry_system_dir(const wchar_t *mock_sysdir)
{
if (!mock_sysdir) {
mock_registry[0] = L'\0';
mock_registry_set = false;
} else {
size_t len = wcslen(mock_sysdir);
if (len > GIT_WIN_PATH_MAX) {
git_error_set(GIT_ERROR_INVALID, "mock path too long");
return -1;
}
wcscpy(mock_registry, mock_sysdir);
mock_registry_set = true;
}
return 0;
}
static int lookup_registry_key(
git_win32_path out,
const HKEY hive,
const wchar_t* key,
const wchar_t *value)
{
HKEY hkey;
DWORD type, size;
int error = GIT_ENOTFOUND;
/*
* Registry data may not be NUL terminated, provide room to do
* it ourselves.
*/
size = (DWORD)((sizeof(git_win32_path) - 1) * sizeof(wchar_t));
if (RegOpenKeyExW(hive, key, 0, KEY_READ, &hkey) != 0)
return GIT_ENOTFOUND;
if (RegQueryValueExW(hkey, value, NULL, &type, (LPBYTE)out, &size) == 0 &&
type == REG_SZ &&
size > 0 &&
size < sizeof(git_win32_path)) {
size_t wsize = size / sizeof(wchar_t);
size_t len = wsize - 1;
if (out[wsize - 1] != L'\0') {
len = wsize;
out[wsize] = L'\0';
}
if (out[len - 1] == L'\\')
out[len - 1] = L'\0';
if (_waccess(out, F_OK) == 0)
error = 0;
}
RegCloseKey(hkey);
return error;
}
static int find_sysdir_in_registry(git_win32_path out)
{
if (mock_registry_set) {
if (mock_registry[0] == L'\0')
return GIT_ENOTFOUND;
wcscpy(out, mock_registry);
return 0;
}
if (lookup_registry_key(out, HKEY_CURRENT_USER, REG_GITFORWINDOWS_KEY, L"InstallLocation") == 0 ||
lookup_registry_key(out, HKEY_CURRENT_USER, REG_GITFORWINDOWS_KEY_WOW64, L"InstallLocation") == 0 ||
lookup_registry_key(out, HKEY_LOCAL_MACHINE, REG_GITFORWINDOWS_KEY, L"InstallLocation") == 0 ||
lookup_registry_key(out, HKEY_LOCAL_MACHINE, REG_GITFORWINDOWS_KEY_WOW64, L"InstallLocation") == 0)
return 0;
return GIT_ENOTFOUND;
}
static int find_sysdir_in_path(git_win32_path out)
{
size_t out_len;
if (git_win32_path_find_executable(out, L"git.exe") < 0 &&
git_win32_path_find_executable(out, L"git.cmd") < 0)
return GIT_ENOTFOUND;
out_len = wcslen(out);
/* Trim the file name */
if (out_len <= CONST_STRLEN(L"git.exe"))
return GIT_ENOTFOUND;
out_len -= CONST_STRLEN(L"git.exe");
if (out_len && out[out_len - 1] == L'\\')
out_len--;
/*
* Git for Windows usually places the command in a 'bin' or
* 'cmd' directory, trim that.
*/
if (out_len >= CONST_STRLEN(L"\\bin") &&
wcsncmp(&out[out_len - CONST_STRLEN(L"\\bin")], L"\\bin", CONST_STRLEN(L"\\bin")) == 0)
out_len -= CONST_STRLEN(L"\\bin");
else if (out_len >= CONST_STRLEN(L"\\cmd") &&
wcsncmp(&out[out_len - CONST_STRLEN(L"\\cmd")], L"\\cmd", CONST_STRLEN(L"\\cmd")) == 0)
out_len -= CONST_STRLEN(L"\\cmd");
if (!out_len)
return GIT_ENOTFOUND;
out[out_len] = L'\0';
return 0;
}
static int win32_find_existing_dirs(
git_str* out,
const wchar_t* tmpl[])
{
git_win32_path path16;
git_str buf = GIT_STR_INIT;
git_str_clear(out);
for (; *tmpl != NULL; tmpl++) {
if (!git_win32__expand_path(path16, *tmpl) &&
path16[0] != L'%' &&
!_waccess(path16, F_OK)) {
win32_path_to_8(&buf, path16);
if (buf.size)
git_str_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr);
}
}
git_str_dispose(&buf);
return (git_str_oom(out) ? -1 : 0);
}
static int append_subdir(git_str *out, git_str *path, const char *subdir)
{
static const char* architecture_roots[] = {
"",
"mingw64",
"mingw32",
NULL
};
const char **root;
size_t orig_path_len = path->size;
for (root = architecture_roots; *root; root++) {
if ((*root[0] && git_str_joinpath(path, path->ptr, *root) < 0) ||
git_str_joinpath(path, path->ptr, subdir) < 0)
return -1;
if (git_fs_path_exists(path->ptr) &&
git_str_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, path->ptr) < 0)
return -1;
git_str_truncate(path, orig_path_len);
}
return 0;
}
int git_win32__find_system_dirs(git_str *out, const char *subdir)
{
git_win32_path pathdir, regdir;
git_str path8 = GIT_STR_INIT;
bool has_pathdir, has_regdir;
int error;
has_pathdir = (find_sysdir_in_path(pathdir) == 0);
has_regdir = (find_sysdir_in_registry(regdir) == 0);
if (!has_pathdir && !has_regdir)
return 0;
/*
* Usually the git in the path is the same git in the registry,
* in this case there's no need to duplicate the paths.
*/
if (has_pathdir && has_regdir && wcscmp(pathdir, regdir) == 0)
has_regdir = false;
if (has_pathdir) {
if ((error = win32_path_to_8(&path8, pathdir)) < 0 ||
(error = append_subdir(out, &path8, subdir)) < 0)
goto done;
}
if (has_regdir) {
if ((error = win32_path_to_8(&path8, regdir)) < 0 ||
(error = append_subdir(out, &path8, subdir)) < 0)
goto done;
}
done:
git_str_dispose(&path8);
return error;
}
int git_win32__find_global_dirs(git_str *out)
{
static const wchar_t *global_tmpls[4] = {
L"%HOME%\\",
L"%HOMEDRIVE%%HOMEPATH%\\",
L"%USERPROFILE%\\",
NULL,
};
return win32_find_existing_dirs(out, global_tmpls);
}
int git_win32__find_xdg_dirs(git_str *out)
{
static const wchar_t *global_tmpls[7] = {
L"%XDG_CONFIG_HOME%\\git",
L"%APPDATA%\\git",
L"%LOCALAPPDATA%\\git",
L"%HOME%\\.config\\git",
L"%HOMEDRIVE%%HOMEPATH%\\.config\\git",
L"%USERPROFILE%\\.config\\git",
NULL,
};
return win32_find_existing_dirs(out, global_tmpls);
}
int git_win32__find_programdata_dirs(git_str *out)
{
static const wchar_t *programdata_tmpls[2] = {
L"%PROGRAMDATA%\\Git",
NULL,
};
return win32_find_existing_dirs(out, programdata_tmpls);
}
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_win32_findfile_h__
#define INCLUDE_win32_findfile_h__
#include "git2_util.h"
/** Sets the mock registry root for Git for Windows for testing. */
extern int git_win32__set_registry_system_dir(const wchar_t *mock_sysdir);
extern int git_win32__find_system_dirs(git_str *out, const char *subpath);
extern int git_win32__find_global_dirs(git_str *out);
extern int git_win32__find_xdg_dirs(git_str *out);
extern int git_win32__find_programdata_dirs(git_str *out);
#endif
......@@ -548,31 +548,61 @@ void clar__assert_equal_file(
(size_t)expected_bytes, (size_t)total_bytes);
}
static git_buf _cl_restore_home = GIT_BUF_INIT;
static git_buf _cl_restore_homedir = GIT_BUF_INIT;
void cl_fake_home_cleanup(void *payload)
void cl_fake_homedir_cleanup(void *payload)
{
GIT_UNUSED(payload);
if (_cl_restore_home.ptr) {
if (_cl_restore_homedir.ptr) {
cl_git_pass(git_libgit2_opts(
GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, _cl_restore_home.ptr));
git_buf_dispose(&_cl_restore_home);
GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, _cl_restore_homedir.ptr));
git_buf_dispose(&_cl_restore_homedir);
}
}
void cl_fake_home(void)
void cl_fake_homedir(void)
{
git_str path = GIT_STR_INIT;
cl_git_pass(git_libgit2_opts(
GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, &_cl_restore_home));
GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, &_cl_restore_homedir));
cl_set_cleanup(cl_fake_home_cleanup, NULL);
cl_set_cleanup(cl_fake_homedir_cleanup, NULL);
if (!git_fs_path_exists("home"))
cl_must_pass(p_mkdir("home", 0777));
cl_git_pass(git_fs_path_prettify(&path, "home", NULL));
if (!git_fs_path_exists("homedir"))
cl_must_pass(p_mkdir("homedir", 0777));
cl_git_pass(git_fs_path_prettify(&path, "homedir", NULL));
cl_git_pass(git_libgit2_opts(
GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr));
git_str_dispose(&path);
}
static git_buf _cl_restore_globalconfig = GIT_BUF_INIT;
void cl_fake_globalconfig_cleanup(void *payload)
{
GIT_UNUSED(payload);
if (_cl_restore_globalconfig.ptr) {
cl_git_pass(git_libgit2_opts(
GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, _cl_restore_globalconfig.ptr));
git_buf_dispose(&_cl_restore_globalconfig);
}
}
void cl_fake_globalconfig(void)
{
git_str path = GIT_STR_INIT;
cl_git_pass(git_libgit2_opts(
GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, &_cl_restore_globalconfig));
cl_set_cleanup(cl_fake_globalconfig_cleanup, NULL);
if (!git_fs_path_exists("globalconfig"))
cl_must_pass(p_mkdir("globalconfig", 0777));
cl_git_pass(git_fs_path_prettify(&path, "globalconfig", NULL));
cl_git_pass(git_libgit2_opts(
GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr));
git_str_dispose(&path);
......
......@@ -213,13 +213,23 @@ int cl_repo_get_bool(git_repository *repo, const char *cfg);
void cl_repo_set_string(git_repository *repo, const char *cfg, const char *value);
/* set up a fake "home" directory and set libgit2 GLOBAL search path.
/* set up a fake "home" directory
*
* automatically configures cleanup function to restore the regular search
* path, although you can call it explicitly if you wish (with NULL).
*/
void cl_fake_home(void);
void cl_fake_home_cleanup(void *);
void cl_fake_homedir(void);
void cl_fake_homedir_cleanup(void *);
/*
* set up a fake directory for the libgit2 GLOBAL search path.
*
* automatically configures cleanup function to restore the regular search
* path, although you can call it explicitly if you wish (with NULL).
*/
void cl_fake_globalconfig(void);
void cl_fake_globalconfig_cleanup(void *);
void cl_sandbox_set_search_path_defaults(void);
void cl_sandbox_disable_ownership_validation(void);
......
......@@ -66,7 +66,7 @@ endif()
include(AddClarTest)
add_clar_test(libgit2_tests offline -v -xonline)
add_clar_test(libgit2_tests invasive -v -score::ftruncate -sfilter::stream::bigfile -sodb::largefiles -siterator::workdir::filesystem_gunk -srepo::init -srepo::init::at_filesystem_root)
add_clar_test(libgit2_tests online -v -sonline -xonline::customcert -xonline::clone::ssh_auth_methods)
add_clar_test(libgit2_tests online -v -sonline -xonline::customcert)
add_clar_test(libgit2_tests online_customcert -v -sonline::customcert)
add_clar_test(libgit2_tests gitdaemon -v -sonline::push)
add_clar_test(libgit2_tests ssh -v -sonline::push -sonline::clone::ssh_cert -sonline::clone::ssh_with_paths -sonline::clone::path_whitespace_ssh -sonline::clone::ssh_auth_methods)
......
......@@ -290,10 +290,10 @@ void test_ignore_path__expand_tilde_to_homedir(void)
assert_is_ignored(false, "example.global_with_tilde");
cl_fake_home();
cl_fake_globalconfig();
/* construct fake home with fake global excludes */
cl_git_mkfile("home/globalexclude", "# found me\n*.global_with_tilde\n");
cl_git_mkfile("globalconfig/globalexclude", "# found me\n*.global_with_tilde\n");
cl_git_pass(git_repository_config(&cfg, g_repo));
cl_git_pass(git_config_set_string(cfg, "core.excludesfile", "~/globalexclude"));
......@@ -305,7 +305,7 @@ void test_ignore_path__expand_tilde_to_homedir(void)
cl_git_pass(git_futils_rmdir_r("home", NULL, GIT_RMDIR_REMOVE_FILES));
cl_fake_home_cleanup(NULL);
cl_fake_globalconfig_cleanup(NULL);
git_attr_cache_flush(g_repo); /* must reset to pick up change */
......
......@@ -385,8 +385,8 @@ void test_ignore_status__leading_slash_ignores(void)
make_test_data(test_repo_1, test_files_1);
cl_fake_home();
cl_git_mkfile("home/.gitignore", "/ignore_me\n");
cl_fake_globalconfig();
cl_git_mkfile("globalconfig/.gitignore", "/ignore_me\n");
{
git_config *cfg;
cl_git_pass(git_repository_config(&cfg, g_repo));
......
......@@ -5,6 +5,7 @@
#include "remote.h"
#include "futils.h"
#include "refs.h"
#include "sysdir.h"
#define LIVE_REPO_URL "http://github.com/libgit2/TestGitRepository"
#define LIVE_EMPTYREPO_URL "http://github.com/libgit2/TestEmptyRepository"
......@@ -35,6 +36,11 @@ static char *_remote_expectcontinue = NULL;
static char *_remote_redirect_initial = NULL;
static char *_remote_redirect_subsequent = NULL;
static char *_github_ssh_pubkey = NULL;
static char *_github_ssh_privkey = NULL;
static char *_github_ssh_passphrase = NULL;
static char *_github_ssh_remotehostkey = NULL;
static int _orig_proxies_need_reset = 0;
static char *_orig_http_proxy = NULL;
static char *_orig_https_proxy = NULL;
......@@ -83,6 +89,11 @@ void test_online_clone__initialize(void)
_remote_redirect_initial = cl_getenv("GITTEST_REMOTE_REDIRECT_INITIAL");
_remote_redirect_subsequent = cl_getenv("GITTEST_REMOTE_REDIRECT_SUBSEQUENT");
_github_ssh_pubkey = cl_getenv("GITTEST_GITHUB_SSH_PUBKEY");
_github_ssh_privkey = cl_getenv("GITTEST_GITHUB_SSH_KEY");
_github_ssh_passphrase = cl_getenv("GITTEST_GITHUB_SSH_PASSPHRASE");
_github_ssh_remotehostkey = cl_getenv("GITTEST_GITHUB_SSH_REMOTE_HOSTKEY");
if (_remote_expectcontinue)
git_libgit2_opts(GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE, 1);
......@@ -116,6 +127,11 @@ void test_online_clone__cleanup(void)
git__free(_remote_redirect_initial);
git__free(_remote_redirect_subsequent);
git__free(_github_ssh_pubkey);
git__free(_github_ssh_privkey);
git__free(_github_ssh_passphrase);
git__free(_github_ssh_remotehostkey);
if (_orig_proxies_need_reset) {
cl_setenv("HTTP_PROXY", _orig_http_proxy);
cl_setenv("HTTPS_PROXY", _orig_https_proxy);
......@@ -537,6 +553,68 @@ static int check_ssh_auth_methods(git_credential **cred, const char *url, const
return GIT_EUSER;
}
static int succeed_certificate_check(git_cert *cert, int valid, const char *host, void *payload)
{
GIT_UNUSED(cert);
GIT_UNUSED(valid);
GIT_UNUSED(payload);
cl_assert_equal_s("github.com", host);
return 0;
}
static int fail_certificate_check(git_cert *cert, int valid, const char *host, void *payload)
{
GIT_UNUSED(cert);
GIT_UNUSED(valid);
GIT_UNUSED(host);
GIT_UNUSED(payload);
return GIT_ECERTIFICATE;
}
static int github_credentials(
git_credential **cred,
const char *url,
const char *username_from_url,
unsigned int allowed_types,
void *data)
{
GIT_UNUSED(url);
GIT_UNUSED(username_from_url);
GIT_UNUSED(data);
if ((allowed_types & GIT_CREDENTIAL_USERNAME) != 0) {
return git_credential_username_new(cred, "git");
}
cl_assert((allowed_types & GIT_CREDENTIAL_SSH_KEY) != 0);
return git_credential_ssh_key_memory_new(cred,
"git",
_github_ssh_pubkey,
_github_ssh_privkey,
_github_ssh_passphrase);
}
void test_online_clone__ssh_github(void)
{
#if !defined(GIT_SSH) || !defined(GIT_SSH_MEMORY_CREDENTIALS)
clar__skip();
#endif
if (!_github_ssh_pubkey || !_github_ssh_privkey)
clar__skip();
cl_fake_homedir();
g_options.fetch_opts.callbacks.credentials = github_credentials;
g_options.fetch_opts.callbacks.certificate_check = succeed_certificate_check;
cl_git_pass(git_clone(&g_repo, SSH_REPO_URL, "./foo", &g_options));
}
void test_online_clone__ssh_auth_methods(void)
{
int with_user;
......@@ -546,7 +624,7 @@ void test_online_clone__ssh_auth_methods(void)
#endif
g_options.fetch_opts.callbacks.credentials = check_ssh_auth_methods;
g_options.fetch_opts.callbacks.payload = &with_user;
g_options.fetch_opts.callbacks.certificate_check = NULL;
g_options.fetch_opts.callbacks.certificate_check = succeed_certificate_check;
with_user = 0;
cl_git_fail_with(GIT_EUSER,
......@@ -557,6 +635,71 @@ void test_online_clone__ssh_auth_methods(void)
git_clone(&g_repo, "ssh://git@github.com/libgit2/TestGitRepository", "./foo", &g_options));
}
/*
* Ensure that the certificate check callback is still called, and
* can accept a host key that is not in the known hosts file.
*/
void test_online_clone__ssh_certcheck_accepts_unknown(void)
{
#if !defined(GIT_SSH) || !defined(GIT_SSH_MEMORY_CREDENTIALS)
clar__skip();
#endif
if (!_github_ssh_pubkey || !_github_ssh_privkey)
clar__skip();
cl_fake_homedir();
g_options.fetch_opts.callbacks.credentials = github_credentials;
/* Ensure we fail without the certificate check */
cl_git_fail_with(GIT_ECERTIFICATE,
git_clone(&g_repo, SSH_REPO_URL, "./foo", NULL));
/* Set the callback to accept the certificate */
g_options.fetch_opts.callbacks.certificate_check = succeed_certificate_check;
cl_git_pass(git_clone(&g_repo, SSH_REPO_URL, "./foo", &g_options));
}
/*
* Ensure that the known hosts file is read and the certificate check
* callback is still called after that.
*/
void test_online_clone__ssh_certcheck_override_knownhosts(void)
{
git_str knownhostsfile = GIT_STR_INIT;
#if !defined(GIT_SSH) || !defined(GIT_SSH_MEMORY_CREDENTIALS)
clar__skip();
#endif
if (!_github_ssh_pubkey || !_github_ssh_privkey || !_github_ssh_remotehostkey)
clar__skip();
g_options.fetch_opts.callbacks.credentials = github_credentials;
cl_fake_homedir();
cl_git_pass(git_sysdir_find_homedir(&knownhostsfile));
cl_git_pass(git_str_joinpath(&knownhostsfile, knownhostsfile.ptr, ".ssh"));
cl_git_pass(p_mkdir(knownhostsfile.ptr, 0777));
cl_git_pass(git_str_joinpath(&knownhostsfile, knownhostsfile.ptr, "known_hosts"));
cl_git_rewritefile(knownhostsfile.ptr, _github_ssh_remotehostkey);
/* Ensure we succeed without the certificate check */
cl_git_pass(git_clone(&g_repo, SSH_REPO_URL, "./foo", &g_options));
git_repository_free(g_repo);
g_repo = NULL;
/* Set the callback to reject the certificate */
g_options.fetch_opts.callbacks.certificate_check = fail_certificate_check;
cl_git_fail_with(GIT_ECERTIFICATE, git_clone(&g_repo, SSH_REPO_URL, "./bar", &g_options));
git_str_dispose(&knownhostsfile);
}
static int custom_remote_ssh_with_paths(
git_remote **out,
git_repository *repo,
......@@ -729,16 +872,6 @@ void test_online_clone__ssh_memory_auth(void)
cl_git_pass(git_clone(&g_repo, _remote_url, "./foo", &g_options));
}
static int fail_certificate_check(git_cert *cert, int valid, const char *host, void *payload)
{
GIT_UNUSED(cert);
GIT_UNUSED(valid);
GIT_UNUSED(host);
GIT_UNUSED(payload);
return GIT_ECERTIFICATE;
}
void test_online_clone__certificate_invalid(void)
{
g_options.fetch_opts.callbacks.certificate_check = fail_certificate_check;
......@@ -752,17 +885,6 @@ void test_online_clone__certificate_invalid(void)
#endif
}
static int succeed_certificate_check(git_cert *cert, int valid, const char *host, void *payload)
{
GIT_UNUSED(cert);
GIT_UNUSED(valid);
GIT_UNUSED(payload);
cl_assert_equal_s("github.com", host);
return 0;
}
void test_online_clone__certificate_valid(void)
{
g_options.fetch_opts.callbacks.certificate_check = succeed_certificate_check;
......
......@@ -132,7 +132,7 @@ static void assert_global_config_match(const char *config, const char *expected)
void test_remote_httpproxy__config_overrides_detached_remote(void)
{
cl_fake_home();
cl_fake_globalconfig();
assert_global_config_match(NULL, NULL);
assert_global_config_match("http.proxy", "http://localhost:1/");
......
#include "clar_libgit2.h"
#include "futils.h"
#include "sysdir.h"
#include "win32/findfile.h"
#ifdef GIT_WIN32
static char *path_save;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment