summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@edwardthomson.com>2023-02-24 00:34:06 +0000
committerEdward Thomson <ethomson@edwardthomson.com>2023-04-11 10:18:16 +0100
commit6b4e046892c52da16b15162e39a66c875c1637bf (patch)
tree01ec5f6a3ed90c4214cca7247280fc9ecc0a4018
parentf2723b28a45424e9b6933d65055f774cd7d5bfd1 (diff)
downloadlibgit2-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.cmake7
-rw-r--r--src/libgit2/transports/ssh.c3
-rw-r--r--src/libgit2/transports/ssh_exec.c267
-rw-r--r--src/libgit2/transports/ssh_exec.h26
-rw-r--r--src/util/git2_features.h.in1
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