From: Stefan Sperling Subject: add ssh -J support to commands which use network To: gameoftrees@openbsd.org Date: Wed, 11 Dec 2024 18:13:59 +0100 Add a -J option to got clone, fetch, send, as well as cvg clone, update, commit. This allows ad-hoc use of SSH jumphosts without having to create entries in ~/.ssh/config. For example, I found this useful when I wanted to fetch from a IPv6-only system while borrowing an IPv4-only wifi connection. ok? M cvg/cvg.1 | 21+ 0- M cvg/cvg.c | 16+ 7- M got/got.1 | 21+ 0- M got/got.c | 18+ 6- M include/got_fetch.h | 2+ 1- M include/got_send.h | 2+ 1- M include/got_worktree_cvg.h | 2+ 2- M lib/dial.c | 7+ 2- M lib/fetch.c | 3+ 2- M lib/got_lib_dial.h | 1+ 1- M lib/send.c | 3+ 2- M lib/worktree_cvg.c | 5+ 5- 12 files changed, 101 insertions(+), 29 deletions(-) commit - 75bd1d8cea00a4cbdbdf6bcea2eb272df2fef76e commit + acf380be9009698bb1246951ede400f9a6d2c937 blob - 265da0a0dd5a94a5dc92044cdc974254621de8fd blob + 5e361ba7f6fa77f9358deaad245b6589b7c35e15 --- cvg/cvg.1 +++ cvg/cvg.1 @@ -135,6 +135,7 @@ working directory. .Cm clone .Op Fl almqv .Op Fl b Ar branch +.Op Fl J Ar jumphost .Op Fl R Ar reference .Ar repository-URL .Op Ar directory @@ -240,6 +241,12 @@ repository's HEAD reference will be fetched. Cannot be used together with the .Fl a option. +.It Fl J Ar jumphost +Specify a +.Ar jumphost +to use with SSH connections. +The same option will be passed to +.Xr ssh 1 . .It Fl l List branches and tags available for fetching from the remote repository and exit immediately. @@ -401,6 +408,7 @@ Silence progress output. .Op Fl q .Op Fl b Ar branch .Op Fl c Ar commit +.Op Fl J Ar jumphost .Op Ar path ... .Xc .Dl Pq alias: Cm up @@ -497,6 +505,12 @@ An abbreviated hash argument will be expanded to a com automatically, provided the abbreviation is unique. If this option is not specified, the most recent commit on the work tree's branch will be used. +.It Fl J Ar jumphost +Specify a +.Ar jumphost +to use with SSH connections. +The same option will be passed to +.Xr ssh 1 . .It Fl q Silence progress output. .El @@ -1287,6 +1301,7 @@ is a directory. .Op Fl CNnS .Op Fl A Ar author .Op Fl F Ar path +.Op Fl J Ar jumphost .Op Fl m Ar message .Op Ar path ... .Xc @@ -1396,6 +1411,12 @@ Use the specified log message when creating the new co Cannot be used together with the .Fl F option. +.It Fl J Ar jumphost +Specify a +.Ar jumphost +to use with SSH connections. +The same option will be passed to +.Xr ssh 1 . .It Fl N This option prevents .Cm got commit blob - 14f6f69962facd1f76c094458fd23bec1fabb9f1 blob + 9f093ace3cfdb472bce06ad93efc12cb9138a9e4 --- cvg/cvg.c +++ cvg/cvg.c @@ -1534,13 +1534,14 @@ cmd_clone(int argc, char *argv[]) int verbosity = 0, fetch_all_branches = 0, mirror_references = 0; int bflag = 0, list_refs_only = 0; int *pack_fds = NULL; + const char *jumphost = NULL; TAILQ_INIT(&refs); TAILQ_INIT(&symrefs); TAILQ_INIT(&wanted_branches); TAILQ_INIT(&wanted_refs); - while ((ch = getopt(argc, argv, "ab:lmqR:v")) != -1) { + while ((ch = getopt(argc, argv, "ab:J:lmqR:v")) != -1) { switch (ch) { case 'a': fetch_all_branches = 1; @@ -1552,6 +1553,9 @@ cmd_clone(int argc, char *argv[]) return error; bflag = 1; break; + case 'J': + jumphost = optarg; + break; case 'l': list_refs_only = 1; break; @@ -1670,7 +1674,7 @@ cmd_clone(int argc, char *argv[]) printf("Connecting to %s\n", git_url); error = got_fetch_connect(&fetchpid, &fetchfd, proto, host, port, - server_path, verbosity); + server_path, jumphost, verbosity); if (error) goto done; @@ -2662,6 +2666,7 @@ cmd_update(int argc, char *argv[]) char *commit_id_str = NULL; const char *refname; struct got_reference *head_ref = NULL; + const char *jumphost = NULL; TAILQ_INIT(&paths); TAILQ_INIT(&refs); @@ -2670,13 +2675,16 @@ cmd_update(int argc, char *argv[]) TAILQ_INIT(&wanted_branches); TAILQ_INIT(&wanted_refs); - while ((ch = getopt(argc, argv, "c:qr:vX")) != -1) { + while ((ch = getopt(argc, argv, "c:J:qr:vX")) != -1) { switch (ch) { case 'c': commit_id_str = strdup(optarg); if (commit_id_str == NULL) return got_error_from_errno("strdup"); break; + case 'J': + jumphost = optarg; + break; case 't': replace_tags = 1; break; @@ -2828,7 +2836,7 @@ cmd_update(int argc, char *argv[]) } error = got_fetch_connect(&fetchpid, &fetchfd, proto, host, port, - server_path, verbosity); + server_path, jumphost, verbosity); if (error) goto done; @@ -7802,7 +7810,7 @@ cmd_commit(int argc, char *argv[]) int nremotes; char *proto = NULL, *host = NULL, *port = NULL; char *repo_name = NULL, *server_path = NULL; - const char *remote_name; + const char *remote_name, *jumphost = NULL; int verbosity = 0; int i; @@ -7816,7 +7824,7 @@ cmd_commit(int argc, char *argv[]) err(1, "pledge"); #endif - while ((ch = getopt(argc, argv, "A:CF:m:NnS")) != -1) { + while ((ch = getopt(argc, argv, "A:CF:J:m:NnS")) != -1) { switch (ch) { case 'A': author = optarg; @@ -8024,7 +8032,8 @@ cmd_commit(int argc, char *argv[]) error = got_worktree_cvg_commit(&id, worktree, &paths, author, committer, allow_bad_symlinks, show_diff, commit_conflicts, collect_commit_logmsg, &cl_arg, print_status, NULL, proto, host, - port, server_path, verbosity, remote, check_cancelled, repo); + port, server_path, jumphost, verbosity, remote, + check_cancelled, repo); if (error) { if (error->code != GOT_ERR_COMMIT_MSG_EMPTY && cl_arg.logmsg_path != NULL) blob - 4983f2ac232429e5c39c59d67d5fc268d186efd1 blob + 8fb89aed8c779a9d24c3c7b907fbedcfae7c7b29 --- got/got.1 +++ got/got.1 @@ -204,6 +204,7 @@ working directory. .Cm clone .Op Fl almqv .Op Fl b Ar branch +.Op Fl J Ar jumphost .Op Fl R Ar reference .Ar repository-URL .Op Ar directory @@ -338,6 +339,12 @@ repository's HEAD reference will be fetched. Cannot be used together with the .Fl a option. +.It Fl J Ar jumphost +Specify a +.Ar jumphost +to use with SSH connections. +The same option will be passed to +.Xr ssh 1 . .It Fl l List branches and tags available for fetching from the remote repository and exit immediately. @@ -425,6 +432,7 @@ The maximum is 3. .Cm fetch .Op Fl adlqtvX .Op Fl b Ar branch +.Op Fl J Ar jumphost .Op Fl R Ar reference .Op Fl r Ar repository-path .Op Ar remote-repository @@ -531,6 +539,12 @@ Any commit, tree, tag, and blob objects belonging to d tags remain in the repository and may be removed separately with Git's garbage collector or .Cm gotadmin cleanup . +.It Fl J Ar jumphost +Specify a +.Ar jumphost +to use with SSH connections. +The same option will be passed to +.Xr ssh 1 . .It Fl l List branches and tags available for fetching from the remote repository and exit immediately. @@ -2331,6 +2345,7 @@ in the repository. .Op Fl afqTv .Op Fl b Ar branch .Op Fl d Ar branch +.Op Fl J Ar jumphost .Op Fl r Ar repository-path .Op Fl t Ar tag .Op Ar remote-repository @@ -2465,6 +2480,12 @@ copy of a branch or tag is known to be out-of-date and disposable. The risks of creating inconsistencies between different repositories should also be taken into account. +.It Fl J Ar jumphost +Specify a +.Ar jumphost +to use with SSH connections. +The same option will be passed to +.Xr ssh 1 . .It Fl q Suppress progress reporting output. The same option will be passed to blob - ed5defc312e6a540e5c6a78407f1fa1d9dce3812 blob + ff35c0c72ff6e7058ac0339162720511890984cc --- got/got.c +++ got/got.c @@ -1627,6 +1627,7 @@ cmd_clone(int argc, char *argv[]) pid_t fetchpid = -1; struct got_fetch_progress_arg fpa; char *git_url = NULL; + const char *jumphost = NULL; int verbosity = 0, fetch_all_branches = 0, mirror_references = 0; int bflag = 0, list_refs_only = 0; int *pack_fds = NULL; @@ -1636,7 +1637,7 @@ cmd_clone(int argc, char *argv[]) TAILQ_INIT(&wanted_branches); TAILQ_INIT(&wanted_refs); - while ((ch = getopt(argc, argv, "ab:lmqR:v")) != -1) { + while ((ch = getopt(argc, argv, "ab:J:lmqR:v")) != -1) { switch (ch) { case 'a': fetch_all_branches = 1; @@ -1648,6 +1649,9 @@ cmd_clone(int argc, char *argv[]) return error; bflag = 1; break; + case 'J': + jumphost = optarg; + break; case 'l': list_refs_only = 1; break; @@ -1766,7 +1770,7 @@ cmd_clone(int argc, char *argv[]) printf("Connecting to %s\n", git_url); error = got_fetch_connect(&fetchpid, &fetchfd, proto, host, port, - server_path, verbosity); + server_path, jumphost, verbosity); if (error) goto done; @@ -2384,6 +2388,7 @@ cmd_fetch(int argc, char *argv[]) int delete_refs = 0, replace_tags = 0, delete_remote = 0; int *pack_fds = NULL, have_bflag = 0; const char *remote_head = NULL, *worktree_branch = NULL; + const char *jumphost = NULL; TAILQ_INIT(&refs); TAILQ_INIT(&symrefs); @@ -2391,7 +2396,7 @@ cmd_fetch(int argc, char *argv[]) TAILQ_INIT(&wanted_branches); TAILQ_INIT(&wanted_refs); - while ((ch = getopt(argc, argv, "ab:dlqR:r:tvX")) != -1) { + while ((ch = getopt(argc, argv, "ab:dJ:lqR:r:tvX")) != -1) { switch (ch) { case 'a': fetch_all_branches = 1; @@ -2406,6 +2411,9 @@ cmd_fetch(int argc, char *argv[]) case 'd': delete_refs = 1; break; + case 'J': + jumphost = optarg; + break; case 'l': list_refs_only = 1; break; @@ -2643,7 +2651,7 @@ cmd_fetch(int argc, char *argv[]) } error = got_fetch_connect(&fetchpid, &fetchfd, proto, host, port, - server_path, verbosity); + server_path, jumphost, verbosity); if (error) goto done; #ifndef PROFILE @@ -9920,6 +9928,7 @@ cmd_send(int argc, char *argv[]) int send_all_branches = 0, send_all_tags = 0; struct got_reference *ref = NULL; int *pack_fds = NULL; + const char *jumphost = NULL; TAILQ_INIT(&branches); TAILQ_INIT(&tags); @@ -9928,7 +9937,7 @@ cmd_send(int argc, char *argv[]) TAILQ_INIT(&delete_args); TAILQ_INIT(&delete_branches); - while ((ch = getopt(argc, argv, "ab:d:fqr:Tt:v")) != -1) { + while ((ch = getopt(argc, argv, "ab:d:fJ:qr:Tt:v")) != -1) { switch (ch) { case 'a': send_all_branches = 1; @@ -9947,6 +9956,9 @@ cmd_send(int argc, char *argv[]) case 'f': overwrite_refs = 1; break; + case 'J': + jumphost = optarg; + break; case 'q': verbosity = -1; break; @@ -10214,7 +10226,7 @@ cmd_send(int argc, char *argv[]) } error = got_send_connect(&sendpid, &sendfd, proto, host, port, - server_path, verbosity); + server_path, jumphost, verbosity); if (error) goto done; blob - d14b5f1032910d954b7d663f20e1d8e0b04b216c blob + abf138991ddab8ea1eae485f37d910d443f8fe76 --- include/got_fetch.h +++ include/got_fetch.h @@ -19,6 +19,7 @@ /* * Attempt to open a connection to a server using the provided protocol * scheme, hostname port number (as a string) and server-side path. + * A jumphost can be specified which will be passed to ssh(1) via -J. * A verbosity level can be specified; it currently controls the amount * of -v options passed to ssh(1). If the level is -1 ssh(1) will be run * with the -q option. @@ -31,7 +32,7 @@ * the process to exit with waitpid(2). Otherwise, return PID -1. */ const struct got_error *got_fetch_connect(pid_t *, int *, const char *, - const char *, const char *, const char *, int); + const char *, const char *, const char *, const char *, int); /* A callback function which gets invoked with progress information to print. */ typedef const struct got_error *(*got_fetch_progress_cb)(void *, blob - 038b51f30308b8ca6879fdbddcfd64c6c83b7594 blob + 9f8c6498d5a65aa365c53f33830bcb336c8c2ee3 --- include/got_send.h +++ include/got_send.h @@ -26,13 +26,14 @@ * * If successful return an open file descriptor for the connection which can * be passed to other functions below, and must be disposed of with close(2). + * A jumphost can be specified which will be passed to ssh(1) via -J. * * If an ssh(1) process was started return its PID as well, in which case * the caller should eventually send SIGTERM to the procress and wait for * the process to exit with waitpid(2). Otherwise, return PID -1. */ const struct got_error *got_send_connect(pid_t *, int *, const char *, - const char *, const char *, const char *, int); + const char *, const char *, const char *, const char *, int); /* A callback function which gets invoked with progress information to print. */ typedef const struct got_error *(*got_send_progress_cb)(void *, blob - 5607eff4903ac486cf8766910d0e6ea7a47f928a blob + 72cd9b87962075687ca6d1b929f6f55cb33236ef --- include/got_worktree_cvg.h +++ include/got_worktree_cvg.h @@ -31,8 +31,8 @@ const struct got_error *got_worktree_cvg_commit(struct struct got_worktree *, struct got_pathlist_head *, const char *, const char *, int, int, int, got_worktree_commit_msg_cb, void *, got_worktree_status_cb, void *, const char *, const char *, const char *, - const char *, int, const struct got_remote_repo *, got_cancel_cb, - struct got_repository *); + const char *, const char *, int, const struct got_remote_repo *, + got_cancel_cb, struct got_repository *); /* * Get the reference name for a temporary commit to be trivially rebased blob - 88b600b1f00ec7c68d10041e64ae7ef76b9baac2 blob + 6f47d70612b97fe473d6aba91f3907153a33dd56 --- lib/dial.c +++ lib/dial.c @@ -261,13 +261,14 @@ escape_path(char *buf, size_t bufsize, const char *pat const struct got_error * got_dial_ssh(pid_t *newpid, int *newfd, const char *host, - const char *port, const char *path, const char *command, int verbosity) + const char *port, const char *path, const char *jumphost, + const char *command, int verbosity) { const struct got_error *error = NULL; int pid, pfd[2]; char cmd[64]; char escaped_path[PATH_MAX]; - const char *argv[11]; + const char *argv[13]; int i = 0, j; *newpid = -1; @@ -289,6 +290,10 @@ got_dial_ssh(pid_t *newpid, int *newfd, const char *ho for (j = 0; j < MIN(3, verbosity); j++) argv[i++] = "-v"; } + if (jumphost) { + argv[i++] = "-J"; + argv[i++] = jumphost; + } argv[i++] = "--"; argv[i++] = (char *)host; argv[i++] = (char *)cmd; blob - 7c67b8bd27d8f0b14c6c6ab064426ba9ca064259 blob + b64070e012cce8f1a3fb519298edb01b361b48eb --- lib/fetch.c +++ lib/fetch.c @@ -79,7 +79,8 @@ const struct got_error * got_fetch_connect(pid_t *fetchpid, int *fetchfd, const char *proto, - const char *host, const char *port, const char *server_path, int verbosity) + const char *host, const char *port, const char *server_path, + const char *jumphost, int verbosity) { const struct got_error *err = NULL; @@ -88,7 +89,7 @@ got_fetch_connect(pid_t *fetchpid, int *fetchfd, const if (strcmp(proto, "ssh") == 0 || strcmp(proto, "git+ssh") == 0) err = got_dial_ssh(fetchpid, fetchfd, host, port, - server_path, GOT_DIAL_CMD_FETCH, verbosity); + server_path, jumphost, GOT_DIAL_CMD_FETCH, verbosity); else if (strcmp(proto, "git") == 0) err = got_dial_git(fetchfd, host, port, server_path, GOT_DIAL_CMD_FETCH); blob - 63de6cd1cf810d258095343358365fa993a0aa82 blob + 2e53b80d6121b5d2e79d4ffedc74c0fb6529a6de --- lib/got_lib_dial.h +++ lib/got_lib_dial.h @@ -23,7 +23,7 @@ const struct got_error *got_dial_git(int *newfd, const const struct got_error *got_dial_ssh(pid_t *newpid, int *newfd, const char *host, const char *port, const char *path, - const char *command, int verbosity); + const char *jumphost, const char *command, int verbosity); const struct got_error *got_dial_http(pid_t *newpid, int *newfd, const char *host, const char *port, const char *path, int, int); blob - f8e87fabb17857a5abf28d64d34330a17b6aefc7 blob + 348d1dbb26e60918ccd5c3ea7b3106ec92916e40 --- lib/send.c +++ lib/send.c @@ -86,7 +86,8 @@ const struct got_error * got_send_connect(pid_t *sendpid, int *sendfd, const char *proto, - const char *host, const char *port, const char *server_path, int verbosity) + const char *host, const char *port, const char *server_path, + const char *jumphost, int verbosity) { const struct got_error *err = NULL; @@ -95,7 +96,7 @@ got_send_connect(pid_t *sendpid, int *sendfd, const ch if (strcmp(proto, "ssh") == 0 || strcmp(proto, "git+ssh") == 0) err = got_dial_ssh(sendpid, sendfd, host, port, server_path, - GOT_DIAL_CMD_SEND, verbosity); + jumphost, GOT_DIAL_CMD_SEND, verbosity); else if (strcmp(proto, "git") == 0) err = got_dial_git(sendfd, host, port, server_path, GOT_DIAL_CMD_SEND); blob - e385e250cc75d449167dd82181f0654e2dcdf4e5 blob + b9abd5bf360259fba46644942a97cd1cc76f4b9f --- lib/worktree_cvg.c +++ lib/worktree_cvg.c @@ -2764,7 +2764,7 @@ done: static const struct got_error * fetch_updated_remote(const char *proto, const char *host, const char *port, - const char *server_path, int verbosity, + const char *server_path, const char *jumphost, int verbosity, const struct got_remote_repo *remote, struct got_repository *repo, struct got_reference *head_ref, const char *head_refname) { @@ -2790,7 +2790,7 @@ fetch_updated_remote(const char *proto, const char *ho goto done; err = got_fetch_connect(&fetchpid, &fetchfd, proto, host, - port, server_path, verbosity); + port, server_path, jumphost, verbosity); if (err) goto done; @@ -2909,7 +2909,7 @@ got_worktree_cvg_commit(struct got_object_id **new_com got_worktree_commit_msg_cb commit_msg_cb, void *commit_arg, got_worktree_status_cb status_cb, void *status_arg, const char *proto, const char *host, const char *port, - const char *server_path, int verbosity, + const char *server_path, const char *jumphost, int verbosity, const struct got_remote_repo *remote, got_cancel_cb check_cancelled, struct got_repository *repo) @@ -3089,7 +3089,7 @@ got_worktree_cvg_commit(struct got_object_id **new_com /* Attempt send to remote branch. */ err = got_send_connect(&sendpid, &sendfd, proto, host, port, - server_path, verbosity); + server_path, jumphost, verbosity); if (err) goto done; @@ -3110,7 +3110,7 @@ got_worktree_cvg_commit(struct got_object_id **new_com * No trivial-rebase yet; require update to be run manually. */ err = fetch_updated_remote(proto, host, port, server_path, - verbosity, remote, repo, head_ref, head_refname); + jumphost, verbosity, remote, repo, head_ref, head_refname); if (err == NULL) goto done; err = got_error(GOT_ERR_COMMIT_OUT_OF_DATE);