diff options
author | Edward Thomson <ethomson@edwardthomson.com> | 2023-02-14 16:20:06 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-02-14 16:20:06 +0000 |
commit | 5b1667a908144e125b10a955b28970869093103f (patch) | |
tree | 666b457d29293c2efe17d63a56bc866f88f8cda3 | |
parent | cd6f679af401eda1f172402006ef8265f8bd58ea (diff) | |
parent | 08ed0881eaef6592d3553dc96d6102f9f0251507 (diff) | |
download | libgit2-5b1667a908144e125b10a955b28970869093103f.tar.gz |
Merge pull request #6476 from libgit2/ethomson/v1.4.6
ssh: backport windows known_hosts fixes to v1.4
-rw-r--r-- | .github/workflows/main.yml | 14 | ||||
-rwxr-xr-x | ci/build.sh | 20 | ||||
-rwxr-xr-x | ci/setup-win32-build.sh | 27 | ||||
-rwxr-xr-x | ci/test.sh | 32 | ||||
-rw-r--r-- | src/sysdir.c | 26 | ||||
-rw-r--r-- | src/sysdir.h | 42 | ||||
-rw-r--r-- | src/transports/ssh.c | 105 | ||||
-rw-r--r-- | tests/CMakeLists.txt | 2 | ||||
-rw-r--r-- | tests/clar_libgit2.c | 52 | ||||
-rw-r--r-- | tests/clar_libgit2.h | 16 | ||||
-rw-r--r-- | tests/ignore/path.c | 6 | ||||
-rw-r--r-- | tests/ignore/status.c | 4 | ||||
-rw-r--r-- | tests/online/clone.c | 166 | ||||
-rw-r--r-- | tests/remote/httpproxy.c | 2 |
14 files changed, 401 insertions, 113 deletions
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index bcad84b8b..a76dd3b3a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -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 diff --git a/ci/build.sh b/ci/build.sh index 21a45af5f..80e7a61ae 100755 --- a/ci/build.sh +++ b/ci/build.sh @@ -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:" diff --git a/ci/setup-win32-build.sh b/ci/setup-win32-build.sh new file mode 100755 index 000000000..a8b81e5ef --- /dev/null +++ b/ci/setup-win32-build.sh @@ -0,0 +1,27 @@ +#!/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" diff --git a/ci/test.sh b/ci/test.sh index 60d94caf8..9bb374a03 100755 --- a/ci/test.sh +++ b/ci/test.sh @@ -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 diff --git a/src/sysdir.c b/src/sysdir.c index 450cb509b..35ea6fe5d 100644 --- a/src/sysdir.c +++ b/src/sysdir.c @@ -75,7 +75,7 @@ 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); @@ -114,6 +114,11 @@ 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 @@ -171,6 +176,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 +356,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 +373,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; +} diff --git a/src/sysdir.h b/src/sysdir.h index 568f27940..cc5962434 100644 --- a/src/sysdir.h +++ b/src/sysdir.h @@ -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; /** diff --git a/src/transports/ssh.c b/src/transports/ssh.c index 85e779744..e90ab07e8 100644 --- a/src/transports/ssh.c +++ b/src/transports/ssh.c @@ -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; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 336fc3b3d..873a8b851 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -70,7 +70,7 @@ endfunction(ADD_CLAR_TEST) add_clar_test(offline -v -xonline) add_clar_test(invasive -v -score::ftruncate -sfilter::stream::bigfile -sodb::largefiles -siterator::workdir::filesystem_gunk -srepo::init -srepo::init::at_filesystem_root) -add_clar_test(online -v -sonline -xonline::customcert -xonline::clone::ssh_auth_methods) +add_clar_test(online -v -sonline -xonline::customcert) add_clar_test(online_customcert -v -sonline::customcert) add_clar_test(gitdaemon -v -sonline::push) add_clar_test(ssh -v -sonline::push -sonline::clone::ssh_cert -sonline::clone::ssh_with_paths -sonline::clone::path_whitespace_ssh -sonline::clone::ssh_auth_methods) diff --git a/tests/clar_libgit2.c b/tests/clar_libgit2.c index 783b457f9..c531b01bc 100644 --- a/tests/clar_libgit2.c +++ b/tests/clar_libgit2.c @@ -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); diff --git a/tests/clar_libgit2.h b/tests/clar_libgit2.h index da3f41524..2e06f19ca 100644 --- a/tests/clar_libgit2.h +++ b/tests/clar_libgit2.h @@ -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); diff --git a/tests/ignore/path.c b/tests/ignore/path.c index a574d1d79..3b95b88ab 100644 --- a/tests/ignore/path.c +++ b/tests/ignore/path.c @@ -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 */ diff --git a/tests/ignore/status.c b/tests/ignore/status.c index deb717590..8214d3847 100644 --- a/tests/ignore/status.c +++ b/tests/ignore/status.c @@ -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)); diff --git a/tests/online/clone.c b/tests/online/clone.c index dfaee0e85..1c5d7cae4 100644 --- a/tests/online/clone.c +++ b/tests/online/clone.c @@ -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; diff --git a/tests/remote/httpproxy.c b/tests/remote/httpproxy.c index f62a2545b..199b6f0b6 100644 --- a/tests/remote/httpproxy.c +++ b/tests/remote/httpproxy.c @@ -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/"); |