cli: add ssh key handling to clone

This commit is contained in:
Edward Thomson
2024-12-12 14:14:20 +00:00
parent daa343c919
commit a24a8479a9

View File

@@ -12,6 +12,8 @@
#include "error.h"
#include "sighandler.h"
#include "progress.h"
#include "console.h"
#include "system.h"
#include "fs_path.h"
#include "futils.h"
@@ -21,7 +23,6 @@
static char *branch, *remote_path, *local_path, *depth;
static int quiet, checkout = 1, bare;
static bool local_path_exists;
static cli_progress progress = CLI_PROGRESS_INIT;
static const cli_opt_spec opts[] = {
CLI_COMMON_OPT,
@@ -44,6 +45,16 @@ static const cli_opt_spec opts[] = {
{ 0 }
};
#define CREDENTIAL_RETRY_MAX 3
struct clone_callback_data {
cli_progress progress;
size_t credential_retries;
git_str password;
};
static struct clone_callback_data callback_data = { CLI_PROGRESS_INIT };
static void print_help(void)
{
cli_opt_usage_fprint(stdout, PROGRAM_NAME, COMMAND_NAME, opts, 0);
@@ -105,7 +116,7 @@ static void cleanup(void)
{
int rmdir_flags = GIT_RMDIR_REMOVE_FILES;
cli_progress_abort(&progress);
cli_progress_abort(&callback_data.progress);
if (local_path_exists)
rmdir_flags |= GIT_RMDIR_SKIP_ROOT;
@@ -122,6 +133,119 @@ static void interrupt_cleanup(void)
exit(130);
}
static int find_keys(git_str *pub, git_str *priv)
{
git_str path = GIT_STR_INIT;
static const char *key_paths[6] = {
"id_dsa", "id_ecdsa", "id_ecdsa_sk",
"id_ed25519", "id_ed25519_sk", "id_rsa"
};
size_t i, path_len;
int error = -1;
if (git_system_homedir(&path) < 0)
goto done;
path_len = git_str_len(&path);
for (i = 0; i < ARRAY_SIZE(key_paths); i++) {
git_str_truncate(&path, path_len);
if (git_str_puts(&path, "/.ssh/") < 0 ||
git_str_puts(&path, key_paths[i]) < 0)
goto done;
if (git_fs_path_exists(path.ptr)) {
if (git_str_puts(priv, path.ptr) < 0 ||
git_str_puts(pub, path.ptr) < 0 ||
git_str_puts(pub, ".pub") < 0)
goto done;
error = 0;
goto done;
}
}
error = GIT_ENOTFOUND;
done:
git_str_dispose(&path);
return error;
}
static int clone_credentials(
git_credential **out,
const char *url,
const char *username_from_url,
unsigned int allowed_types,
void *payload)
{
struct clone_callback_data *data = (struct clone_callback_data *)payload;
git_str pubkey = GIT_STR_INIT, privkey = GIT_STR_INIT,
prompt = GIT_STR_INIT;
int error = GIT_PASSTHROUGH;
GIT_UNUSED(url);
if (++data->credential_retries > CREDENTIAL_RETRY_MAX) {
cli_error("authentication failed");
error = GIT_EUSER;
goto done;
}
if ((allowed_types & GIT_CREDENTIAL_SSH_KEY)) {
if ((error = find_keys(&pubkey, &privkey)) < 0) {
if (error == GIT_ENOTFOUND) {
cli_error("could not find ssh keys for authentication");
error = GIT_EUSER;
}
goto done;
}
if ((error = git_str_printf(&prompt, "Enter passphrase for key '%s': ", pubkey.ptr)) < 0 ||
(error = cli_console_getpass(&data->password, prompt.ptr)) < 0)
goto done;
error = git_credential_ssh_key_new(out,
username_from_url,
pubkey.ptr,
privkey.ptr,
data->password.ptr);
}
done:
git_str_zero(&data->password);
git_str_dispose(&prompt);
git_str_dispose(&pubkey);
git_str_dispose(&privkey);
return error;
}
static int clone_progress_sideband(const char *str, int len, void *payload)
{
struct clone_callback_data *data = (struct clone_callback_data *)payload;
return cli_progress_fetch_sideband(str, len, &data->progress);
}
static int clone_progress_transfer(
const git_indexer_progress *stats,
void *payload)
{
struct clone_callback_data *data = (struct clone_callback_data *)payload;
return cli_progress_fetch_transfer(stats, &data->progress);
}
static void clone_progress_checkout(
const char *path,
size_t completed_steps,
size_t total_steps,
void *payload)
{
struct clone_callback_data *data = (struct clone_callback_data *)payload;
return cli_progress_checkout(path, completed_steps, total_steps, &data->progress);
}
int cmd_clone(int argc, char **argv)
{
git_clone_options clone_opts = GIT_CLONE_OPTIONS_INIT;
@@ -164,26 +288,31 @@ int cmd_clone(int argc, char **argv)
}
if (!quiet) {
clone_opts.fetch_opts.callbacks.sideband_progress = cli_progress_fetch_sideband;
clone_opts.fetch_opts.callbacks.transfer_progress = cli_progress_fetch_transfer;
clone_opts.fetch_opts.callbacks.payload = &progress;
clone_opts.fetch_opts.callbacks.credentials = clone_credentials;
clone_opts.fetch_opts.callbacks.sideband_progress = clone_progress_sideband;
clone_opts.fetch_opts.callbacks.transfer_progress = clone_progress_transfer;
clone_opts.fetch_opts.callbacks.payload = &callback_data;
clone_opts.checkout_opts.progress_cb = cli_progress_checkout;
clone_opts.checkout_opts.progress_payload = &progress;
clone_opts.checkout_opts.progress_cb = clone_progress_checkout;
clone_opts.checkout_opts.progress_payload = &callback_data;
printf("Cloning into '%s'...\n", local_path);
}
if (git_clone(&repo, remote_path, local_path, &clone_opts) < 0) {
if ((ret = git_clone(&repo, remote_path, local_path, &clone_opts)) < 0) {
cleanup();
ret = cli_error_git();
if (ret != GIT_EUSER)
ret = cli_error_git();
goto done;
}
cli_progress_finish(&progress);
cli_progress_finish(&callback_data.progress);
done:
cli_progress_dispose(&progress);
cli_progress_dispose(&callback_data.progress);
git_str_zero(&callback_data.password);
git__free(computed_path);
git_repository_free(repo);
return ret;