diff options
author | Edward Thomson <ethomson@edwardthomson.com> | 2023-02-24 00:34:06 +0000 |
---|---|---|
committer | Edward Thomson <ethomson@edwardthomson.com> | 2023-04-11 10:18:16 +0100 |
commit | 6b4e046892c52da16b15162e39a66c875c1637bf (patch) | |
tree | 01ec5f6a3ed90c4214cca7247280fc9ecc0a4018 | |
parent | f2723b28a45424e9b6933d65055f774cd7d5bfd1 (diff) | |
download | libgit2-6b4e046892c52da16b15162e39a66c875c1637bf.tar.gz |
ssh: introduce GIT_SSH_EXEC for external OpenSSH
We can now use the `git_process` class to invoke OpenSSH and use it as
an SSH transport. This may be preferred over libssh2 for a variety of
callers.
-rw-r--r-- | cmake/SelectSSH.cmake | 7 | ||||
-rw-r--r-- | src/libgit2/transports/ssh.c | 3 | ||||
-rw-r--r-- | src/libgit2/transports/ssh_exec.c | 267 | ||||
-rw-r--r-- | src/libgit2/transports/ssh_exec.h | 26 | ||||
-rw-r--r-- | src/util/git2_features.h.in | 1 |
5 files changed, 302 insertions, 2 deletions
diff --git a/cmake/SelectSSH.cmake b/cmake/SelectSSH.cmake index 684116200..ca0599e01 100644 --- a/cmake/SelectSSH.cmake +++ b/cmake/SelectSSH.cmake @@ -1,5 +1,8 @@ -# find libssh2 -if(USE_SSH STREQUAL ON OR USE_SSH STREQUAL "libssh2") +if(USE_SSH STREQUAL "exec") + set(GIT_SSH_EXEC 1) + + add_feature_info(SSH ON "using OpenSSH exec support") +elseif(USE_SSH STREQUAL ON OR USE_SSH STREQUAL "libssh2") find_pkglibraries(LIBSSH2 libssh2) if(NOT LIBSSH2_FOUND) find_package(LibSSH2) diff --git a/src/libgit2/transports/ssh.c b/src/libgit2/transports/ssh.c index 7171e9cb3..98e1be2d1 100644 --- a/src/libgit2/transports/ssh.c +++ b/src/libgit2/transports/ssh.c @@ -5,6 +5,7 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "ssh_exec.h" #include "ssh_libssh2.h" #include "transports/smart.h" @@ -16,6 +17,8 @@ int git_smart_subtransport_ssh( { #ifdef GIT_SSH_LIBSSH2 return git_smart_subtransport_ssh_libssh2(out, owner, param); +#elif GIT_SSH_EXEC + return git_smart_subtransport_ssh_exec(out, owner, param); #else GIT_UNUSED(out); GIT_UNUSED(owner); diff --git a/src/libgit2/transports/ssh_exec.c b/src/libgit2/transports/ssh_exec.c new file mode 100644 index 000000000..45c6bd58b --- /dev/null +++ b/src/libgit2/transports/ssh_exec.c @@ -0,0 +1,267 @@ +/* + * 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 "ssh_exec.h" + +#ifdef GIT_SSH_EXEC + +#include "common.h" + +#include "net.h" +#include "path.h" +#include "futils.h" +#include "process.h" + +typedef struct { + git_smart_subtransport_stream parent; +} ssh_exec_subtransport_stream; + +typedef struct { + git_smart_subtransport parent; + git_transport *owner; + + ssh_exec_subtransport_stream *current_stream; + + git_smart_service_t action; + git_process *process; +} ssh_exec_subtransport; + +static int ssh_exec_subtransport_stream_read( + git_smart_subtransport_stream *s, + char *buffer, + size_t buf_size, + size_t *bytes_read) +{ + ssh_exec_subtransport *transport; + ssh_exec_subtransport_stream *stream = (ssh_exec_subtransport_stream *)s; + ssize_t ret; + + GIT_ASSERT_ARG(stream); + GIT_ASSERT(stream->parent.subtransport); + + transport = (ssh_exec_subtransport *)stream->parent.subtransport; + + if ((ret = git_process_read(transport->process, buffer, buf_size)) < 0) + return (int)ret; + + *bytes_read = (size_t)ret; + return 0; +} + +static int ssh_exec_subtransport_stream_write( + git_smart_subtransport_stream *s, + const char *buffer, + size_t len) +{ + ssh_exec_subtransport *transport; + ssh_exec_subtransport_stream *stream = (ssh_exec_subtransport_stream *)s; + ssize_t ret; + + GIT_ASSERT(stream && stream->parent.subtransport); + + transport = (ssh_exec_subtransport *)stream->parent.subtransport; + + while (len > 0) { + if ((ret = git_process_write(transport->process, buffer, len)) < 0) + return (int)ret; + + len -= ret; + } + + return 0; +} + +static void ssh_exec_subtransport_stream_free(git_smart_subtransport_stream *s) +{ + ssh_exec_subtransport_stream *stream = (ssh_exec_subtransport_stream *)s; + + git__free(stream); +} + +static int ssh_exec_subtransport_stream_init( + ssh_exec_subtransport_stream **out, + ssh_exec_subtransport *transport) +{ + GIT_ASSERT_ARG(out); + + *out = git__calloc(sizeof(ssh_exec_subtransport_stream), 1); + GIT_ERROR_CHECK_ALLOC(*out); + + (*out)->parent.subtransport = &transport->parent; + (*out)->parent.read = ssh_exec_subtransport_stream_read; + (*out)->parent.write = ssh_exec_subtransport_stream_write; + (*out)->parent.free = ssh_exec_subtransport_stream_free; + + return 0; +} + +GIT_INLINE(int) ensure_transport_state( + ssh_exec_subtransport *transport, + git_smart_service_t expected) +{ + if (transport->action != expected) { + git_error_set(GIT_ERROR_NET, "invalid transport state"); + return -1; + } + + return 0; +} + +static int start_ssh( + ssh_exec_subtransport *transport, + git_smart_service_t action, + const char *sshpath) +{ + const char *args[4]; + const char *env[] = { "GIT_DIR=" }; + + git_process_options process_opts = GIT_PROCESS_OPTIONS_INIT; + git_net_url url = GIT_NET_URL_INIT; + git_str userhost = GIT_BUF_INIT; + const char *command; + int error; + + process_opts.capture_in = 1; + process_opts.capture_out = 1; + process_opts.capture_err = 1; + + switch (action) { + case GIT_SERVICE_UPLOADPACK_LS: + command = "git-upload-pack"; + break; + case GIT_SERVICE_RECEIVEPACK_LS: + command = "git-receive-pack"; + break; + default: + git_error_set(GIT_ERROR_NET, "invalid action"); + error = -1; + goto done; + } + + if (git_net_str_is_url(sshpath)) + error = git_net_url_parse(&url, sshpath); + else + error = git_net_url_parse_scp(&url, sshpath); + + if (error < 0) + goto done; + + if (url.username) { + git_str_puts(&userhost, url.username); + git_str_putc(&userhost, '@'); + } + git_str_puts(&userhost, url.host); + + args[0] = "/usr/bin/ssh"; + args[1] = userhost.ptr; + args[2] = command; + args[3] = url.path; + + if ((error = git_process_new(&transport->process, args, ARRAY_SIZE(args), env, ARRAY_SIZE(env), &process_opts)) < 0 || + (error = git_process_start(transport->process)) < 0) { + git_process_free(transport->process); + transport->process = NULL; + goto done; + } + +done: + git_str_dispose(&userhost); + git_net_url_dispose(&url); + return error; +} + +static int ssh_exec_subtransport_action( + git_smart_subtransport_stream **out, + git_smart_subtransport *t, + const char *sshpath, + git_smart_service_t action) +{ + ssh_exec_subtransport *transport = (ssh_exec_subtransport *)t; + ssh_exec_subtransport_stream *stream = NULL; + git_smart_service_t expected; + int error; + + switch (action) { + case GIT_SERVICE_UPLOADPACK_LS: + case GIT_SERVICE_RECEIVEPACK_LS: + if ((error = ensure_transport_state(transport, 0)) < 0 || + (error = ssh_exec_subtransport_stream_init(&stream, transport)) < 0 || + (error = start_ssh(transport, action, sshpath)) < 0) + goto on_error; + + transport->current_stream = stream; + break; + + case GIT_SERVICE_UPLOADPACK: + case GIT_SERVICE_RECEIVEPACK: + expected = (action == GIT_SERVICE_UPLOADPACK) ? + GIT_SERVICE_UPLOADPACK_LS : GIT_SERVICE_RECEIVEPACK_LS; + + if ((error = ensure_transport_state(transport, expected)) < 0) + goto on_error; + + break; + + default: + git_error_set(GIT_ERROR_INVALID, "invalid service request"); + goto on_error; + } + + transport->action = action; + *out = &transport->current_stream->parent; + + return 0; + +on_error: + if (stream != NULL) + ssh_exec_subtransport_stream_free(&stream->parent); + + return -1; +} + +static int ssh_exec_subtransport_close(git_smart_subtransport *t) +{ + ssh_exec_subtransport *transport = (ssh_exec_subtransport *)t; + + if (transport->process) { + git_process_close(transport->process); + git_process_free(transport->process); + transport->process = NULL; + } + + return 0; +} + +static void ssh_exec_subtransport_free(git_smart_subtransport *t) +{ + ssh_exec_subtransport *transport = (ssh_exec_subtransport *)t; + + git__free(transport); +} + +int git_smart_subtransport_ssh_exec( + git_smart_subtransport **out, + git_transport *owner, + void *payload) +{ + ssh_exec_subtransport *transport; + + GIT_UNUSED(payload); + + transport = git__calloc(sizeof(ssh_exec_subtransport), 1); + GIT_ERROR_CHECK_ALLOC(transport); + + transport->owner = owner; + transport->parent.action = ssh_exec_subtransport_action; + transport->parent.close = ssh_exec_subtransport_close; + transport->parent.free = ssh_exec_subtransport_free; + + *out = (git_smart_subtransport *) transport; + return 0; +} + +#endif diff --git a/src/libgit2/transports/ssh_exec.h b/src/libgit2/transports/ssh_exec.h new file mode 100644 index 000000000..4bcba06b1 --- /dev/null +++ b/src/libgit2/transports/ssh_exec.h @@ -0,0 +1,26 @@ +/* + * 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_transports_ssh_exec_h__ +#define INCLUDE_transports_ssh_exec_h__ + +#include "common.h" + +#include "git2.h" +#include "git2/transport.h" +#include "git2/sys/transport.h" + +int git_smart_subtransport_ssh_exec( + git_smart_subtransport **out, + git_transport *owner, + void *param); + +int git_smart_subtransport_ssh_exec_set_paths( + git_smart_subtransport *subtransport, + const char *cmd_uploadpack, + const char *cmd_receivepack); + +#endif diff --git a/src/util/git2_features.h.in b/src/util/git2_features.h.in index 34d4186cb..bef83a401 100644 --- a/src/util/git2_features.h.in +++ b/src/util/git2_features.h.in @@ -29,6 +29,7 @@ #cmakedefine GIT_QSORT_S #cmakedefine GIT_SSH 1 +#cmakedefine GIT_SSH_EXEC 1 #cmakedefine GIT_SSH_LIBSSH2 1 #cmakedefine GIT_SSH_LIBSSH2_MEMORY_CREDENTIALS 1 |